@ohif/app 3.0.0 → 3.5.0
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/151.bundle.07bac9172580a60fae7a.js +2579 -0
- package/dist/192.bundle.62be5f0ef9705a485071.js +894 -0
- package/dist/199.bundle.2286f24cf0a068e7f50c.js +480 -0
- package/dist/205.bundle.39e6c847d618ad2b1b7a.js +62 -0
- package/dist/208.bundle.23748a85dfdc79c05d3a.js +864 -0
- package/dist/270.bundle.abbdb5348274bae3e8bc.js +23906 -0
- package/dist/283.bundle.33f99a75a5e2d9333da2.js +2939 -0
- package/dist/295.bundle.5105ce962be15c92484d.js +48 -0
- package/dist/331.bundle.7ac7b142d249d14fd99e.js +73034 -0
- package/dist/351.bundle.c5d7279ef42e30f61e08.js +1471 -0
- package/dist/351.css +3 -0
- package/dist/36785fbd89b0e17f6099.wasm +0 -0
- package/dist/381.bundle.0905e683605fcbc0895f.js +1009 -0
- package/dist/404.bundle.0f7a500421f246153d89.js +706 -0
- package/dist/50.bundle.4cb103cd20f5ffccf927.js +324 -0
- package/dist/5004fdc02f329ce53b69.wasm +0 -0
- package/dist/531.bundle.1bc152c87c7e2e987d2b.js +5935 -0
- package/dist/55.bundle.a5a215e13a8511f7aee7.js +685 -0
- package/dist/55.css +3 -0
- package/dist/569.bundle.d147c0aa0604f8ea2094.js +514 -0
- package/dist/581.bundle.646c89c5c3e3ee096363.js +508 -0
- package/dist/606.bundle.5d876f5f3dd8287f0a28.js +4939 -0
- package/dist/610.min.worker.js +2 -0
- package/dist/610.min.worker.js.map +1 -0
- package/dist/616.bundle.bec4736d8c9513e62856.js +686 -0
- package/dist/62ab5d58a2bea7b5a1dc.wasm +0 -0
- package/dist/642.bundle.030d908e22c8ff5611f3.js +169 -0
- package/dist/65916ef3def695744bda.wasm +0 -0
- package/dist/664.bundle.4792c88ae0d6d4b5ed13.js +901 -0
- package/dist/707.bundle.0a74aa3e61ed002eb3c6.js +9049 -0
- package/dist/707.css +1 -0
- package/dist/728.bundle.d13856835357400fef82.js +26221 -0
- package/dist/744.bundle.53b07e48e07a11e920ac.js +2355 -0
- package/dist/75788f12450d4c5ed494.wasm +0 -0
- package/dist/75a0c2dfe07b824c7d21.wasm +0 -0
- package/dist/780.bundle.f60ac1906e0ae080dee8.js +4769 -0
- package/dist/790.bundle.b4df2c5d78a2a565b150.js +454 -0
- package/dist/799.bundle.3fff638815e355b0bdfd.js +271 -0
- package/dist/806.css +1 -0
- package/dist/82.bundle.a24015533196e05d190e.js +6104 -0
- package/dist/917.bundle.a094ae9e9de6df4119ae.js +196 -0
- package/dist/926.bundle.dbc9d0e591cb9217fda2.js +72552 -0
- package/dist/935.bundle.deeffff0e4f7b528e3c3.js +1849 -0
- package/dist/945.min.worker.js +2 -0
- package/dist/945.min.worker.js.map +1 -0
- package/dist/953.bundle.c14d9eb6400f697019ee.js +449 -0
- package/dist/973.bundle.4100cf103686b64938d1.js +261 -0
- package/dist/976.bundle.2720eb892514e1818018.js +2725 -0
- package/dist/984.bundle.157fc66ea5040e1364af.js +1842 -0
- package/dist/_headers +6 -0
- package/dist/_redirects +6 -0
- package/dist/app-config.js +215 -0
- package/dist/app.bundle.253eeb2a7ee986e89c50.js +154621 -0
- package/dist/app.bundle.css +21 -0
- package/dist/assets/android-chrome-144x144.png +0 -0
- package/dist/assets/android-chrome-192x192.png +0 -0
- package/dist/assets/android-chrome-256x256.png +0 -0
- package/dist/assets/android-chrome-36x36.png +0 -0
- package/dist/assets/android-chrome-384x384.png +0 -0
- package/dist/assets/android-chrome-48x48.png +0 -0
- package/dist/assets/android-chrome-512x512.png +0 -0
- package/dist/assets/android-chrome-72x72.png +0 -0
- package/dist/assets/android-chrome-96x96.png +0 -0
- package/dist/assets/apple-touch-icon-1024x1024.png +0 -0
- package/dist/assets/apple-touch-icon-114x114.png +0 -0
- package/dist/assets/apple-touch-icon-120x120.png +0 -0
- package/dist/assets/apple-touch-icon-144x144.png +0 -0
- package/dist/assets/apple-touch-icon-152x152.png +0 -0
- package/dist/assets/apple-touch-icon-167x167.png +0 -0
- package/dist/assets/apple-touch-icon-180x180.png +0 -0
- package/dist/assets/apple-touch-icon-57x57.png +0 -0
- package/dist/assets/apple-touch-icon-60x60.png +0 -0
- package/dist/assets/apple-touch-icon-72x72.png +0 -0
- package/dist/assets/apple-touch-icon-76x76.png +0 -0
- package/dist/assets/apple-touch-icon-precomposed.png +0 -0
- package/dist/assets/apple-touch-icon.png +0 -0
- package/dist/assets/apple-touch-startup-image-1182x2208.png +0 -0
- package/dist/assets/apple-touch-startup-image-1242x2148.png +0 -0
- package/dist/assets/apple-touch-startup-image-1496x2048.png +0 -0
- package/dist/assets/apple-touch-startup-image-1536x2008.png +0 -0
- package/dist/assets/apple-touch-startup-image-320x460.png +0 -0
- package/dist/assets/apple-touch-startup-image-640x1096.png +0 -0
- package/dist/assets/apple-touch-startup-image-640x920.png +0 -0
- package/dist/assets/apple-touch-startup-image-748x1024.png +0 -0
- package/dist/assets/apple-touch-startup-image-750x1294.png +0 -0
- package/dist/assets/apple-touch-startup-image-768x1004.png +0 -0
- package/dist/assets/browserconfig.xml +12 -0
- package/dist/assets/coast-228x228.png +0 -0
- package/dist/assets/favicon-16x16.png +0 -0
- package/dist/assets/favicon-32x32.png +0 -0
- package/dist/assets/favicon.ico +0 -0
- package/dist/assets/firefox_app_128x128.png +0 -0
- package/dist/assets/firefox_app_512x512.png +0 -0
- package/dist/assets/firefox_app_60x60.png +0 -0
- package/dist/assets/manifest.webapp +14 -0
- package/dist/assets/mstile-144x144.png +0 -0
- package/dist/assets/mstile-150x150.png +0 -0
- package/dist/assets/mstile-310x150.png +0 -0
- package/dist/assets/mstile-310x310.png +0 -0
- package/dist/assets/mstile-70x70.png +0 -0
- package/dist/assets/yandex-browser-50x50.png +0 -0
- package/dist/assets/yandex-browser-manifest.json +9 -0
- package/dist/b6b803111e2d06a825bd.wasm +0 -0
- package/dist/c22b37c3488e1d6c3aa4.wasm +0 -0
- package/dist/cornerstoneDICOMImageLoader.min.js +2 -0
- package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -0
- package/dist/dicom-microscopy-viewer.bundle.aa60bdf008c32c39cfd7.js +12 -0
- package/dist/dicomMicroscopyViewer.min.js +3 -0
- package/dist/dicomMicroscopyViewer.min.js.LICENSE.txt +29 -0
- package/dist/es6-shim.min.js +12 -0
- package/dist/google.js +75 -0
- package/dist/index.html +1 -0
- package/dist/index.worker.ea71efba2ce63c499055.worker.js +2 -0
- package/dist/index.worker.ea71efba2ce63c499055.worker.js.map +1 -0
- package/dist/index.worker.min.worker.js +2 -0
- package/dist/index.worker.min.worker.js.map +1 -0
- package/dist/init-service-worker.js +59 -0
- package/dist/manifest.json +59 -0
- package/dist/ohif-logo-light.svg +15 -0
- package/dist/ohif-logo.svg +15 -0
- package/dist/oidc-client.min.js +46 -0
- package/dist/polyfill.min.js +1 -0
- package/dist/silent-refresh.html +16 -0
- package/dist/sw.js +56 -0
- package/package.json +24 -23
|
@@ -0,0 +1,1471 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[351],{
|
|
3
|
+
|
|
4
|
+
/***/ 30351:
|
|
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__(32735);
|
|
17
|
+
// EXTERNAL MODULE: ../../../node_modules/react-resize-detector/build/index.esm.js
|
|
18
|
+
var index_esm = __webpack_require__(14664);
|
|
19
|
+
// EXTERNAL MODULE: ../../../node_modules/prop-types/index.js
|
|
20
|
+
var prop_types = __webpack_require__(60216);
|
|
21
|
+
var prop_types_default = /*#__PURE__*/__webpack_require__.n(prop_types);
|
|
22
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js + 321 modules
|
|
23
|
+
var esm = __webpack_require__(57270);
|
|
24
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 335 modules
|
|
25
|
+
var dist_esm = __webpack_require__(77331);
|
|
26
|
+
// EXTERNAL MODULE: ../../core/src/index.ts + 101 modules
|
|
27
|
+
var src = __webpack_require__(48501);
|
|
28
|
+
// EXTERNAL MODULE: ../../ui/src/index.js + 449 modules
|
|
29
|
+
var ui_src = __webpack_require__(43803);
|
|
30
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone/src/state.ts
|
|
31
|
+
var state = __webpack_require__(21922);
|
|
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(_ref) {
|
|
42
|
+
let {
|
|
43
|
+
viewportData,
|
|
44
|
+
viewportIndex,
|
|
45
|
+
element,
|
|
46
|
+
imageSliceData,
|
|
47
|
+
setImageSliceData,
|
|
48
|
+
scrollbarHeight,
|
|
49
|
+
servicesManager
|
|
50
|
+
} = _ref;
|
|
51
|
+
const {
|
|
52
|
+
cineService,
|
|
53
|
+
cornerstoneViewportService
|
|
54
|
+
} = servicesManager.services;
|
|
55
|
+
const onImageScrollbarChange = (imageIndex, viewportIndex) => {
|
|
56
|
+
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
57
|
+
const viewportId = viewportInfo.getViewportId();
|
|
58
|
+
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
|
59
|
+
const {
|
|
60
|
+
isCineEnabled
|
|
61
|
+
} = cineService.getState();
|
|
62
|
+
if (isCineEnabled) {
|
|
63
|
+
// on image scrollbar change, stop the CINE if it is playing
|
|
64
|
+
cineService.stopClip(element);
|
|
65
|
+
cineService.setCine({
|
|
66
|
+
id: viewportIndex,
|
|
67
|
+
isPlaying: false
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
esm.utilities.jumpToSlice(viewport.element, {
|
|
71
|
+
imageIndex,
|
|
72
|
+
debounceLoading: true
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
(0,react.useEffect)(() => {
|
|
76
|
+
if (!viewportData) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex(viewportIndex);
|
|
80
|
+
if (!viewport) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (viewportData.viewportType === dist_esm.Enums.ViewportType.STACK) {
|
|
84
|
+
const imageIndex = viewport.getCurrentImageIdIndex();
|
|
85
|
+
setImageSliceData({
|
|
86
|
+
imageIndex: imageIndex,
|
|
87
|
+
numberOfSlices: viewportData.data.imageIds.length
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (viewportData.viewportType === dist_esm.Enums.ViewportType.ORTHOGRAPHIC) {
|
|
92
|
+
const sliceData = dist_esm.utilities.getImageSliceDataForVolumeViewport(viewport);
|
|
93
|
+
if (!sliceData) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const {
|
|
97
|
+
imageIndex,
|
|
98
|
+
numberOfSlices
|
|
99
|
+
} = sliceData;
|
|
100
|
+
setImageSliceData({
|
|
101
|
+
imageIndex,
|
|
102
|
+
numberOfSlices
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}, [viewportIndex, viewportData]);
|
|
106
|
+
(0,react.useEffect)(() => {
|
|
107
|
+
if (viewportData?.viewportType !== dist_esm.Enums.ViewportType.STACK) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const updateStackIndex = event => {
|
|
111
|
+
const {
|
|
112
|
+
newImageIdIndex
|
|
113
|
+
} = event.detail;
|
|
114
|
+
// find the index of imageId in the imageIds
|
|
115
|
+
setImageSliceData({
|
|
116
|
+
imageIndex: newImageIdIndex,
|
|
117
|
+
numberOfSlices: viewportData.data.imageIds.length
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
element.addEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
|
|
121
|
+
return () => {
|
|
122
|
+
element.removeEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
|
|
123
|
+
};
|
|
124
|
+
}, [viewportData, element]);
|
|
125
|
+
(0,react.useEffect)(() => {
|
|
126
|
+
if (viewportData?.viewportType !== dist_esm.Enums.ViewportType.ORTHOGRAPHIC) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const updateVolumeIndex = event => {
|
|
130
|
+
const {
|
|
131
|
+
imageIndex,
|
|
132
|
+
numberOfSlices
|
|
133
|
+
} = event.detail;
|
|
134
|
+
// find the index of imageId in the imageIds
|
|
135
|
+
setImageSliceData({
|
|
136
|
+
imageIndex,
|
|
137
|
+
numberOfSlices
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
element.addEventListener(dist_esm.Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
|
|
141
|
+
return () => {
|
|
142
|
+
element.removeEventListener(dist_esm.Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
|
|
143
|
+
};
|
|
144
|
+
}, [viewportData, element]);
|
|
145
|
+
return /*#__PURE__*/react.createElement(ui_src/* ImageScrollbar */.Ln, {
|
|
146
|
+
onChange: evt => onImageScrollbarChange(evt, viewportIndex),
|
|
147
|
+
max: imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0,
|
|
148
|
+
height: scrollbarHeight,
|
|
149
|
+
value: imageSliceData.imageIndex
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
CornerstoneImageScrollbar.propTypes = {
|
|
153
|
+
viewportData: (prop_types_default()).object,
|
|
154
|
+
viewportIndex: (prop_types_default()).number.isRequired,
|
|
155
|
+
element: prop_types_default().instanceOf(Element),
|
|
156
|
+
scrollbarHeight: (prop_types_default()).string,
|
|
157
|
+
imageSliceData: (prop_types_default()).object.isRequired,
|
|
158
|
+
setImageSliceData: (prop_types_default()).func.isRequired,
|
|
159
|
+
servicesManager: (prop_types_default()).object.isRequired
|
|
160
|
+
};
|
|
161
|
+
/* harmony default export */ const ViewportImageScrollbar = (CornerstoneImageScrollbar);
|
|
162
|
+
// EXTERNAL MODULE: ../../../node_modules/gl-matrix/esm/index.js + 10 modules
|
|
163
|
+
var gl_matrix_esm = __webpack_require__(88256);
|
|
164
|
+
// EXTERNAL MODULE: ../../../node_modules/moment/moment.js
|
|
165
|
+
var moment = __webpack_require__(53806);
|
|
166
|
+
var moment_default = /*#__PURE__*/__webpack_require__.n(moment);
|
|
167
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/utils.ts
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Checks if value is valid.
|
|
173
|
+
*
|
|
174
|
+
* @param {number} value
|
|
175
|
+
* @returns {boolean} is valid.
|
|
176
|
+
*/
|
|
177
|
+
function isValidNumber(value) {
|
|
178
|
+
return typeof value === 'number' && !isNaN(value);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Formats number precision.
|
|
183
|
+
*
|
|
184
|
+
* @param {number} number
|
|
185
|
+
* @param {number} precision
|
|
186
|
+
* @returns {number} formatted number.
|
|
187
|
+
*/
|
|
188
|
+
function formatNumberPrecision(number) {
|
|
189
|
+
let precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
190
|
+
if (number !== null) {
|
|
191
|
+
return parseFloat(number).toFixed(precision);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Formats DICOM date.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} date
|
|
199
|
+
* @param {string} strFormat
|
|
200
|
+
* @returns {string} formatted date.
|
|
201
|
+
*/
|
|
202
|
+
function formatDICOMDate(date) {
|
|
203
|
+
let strFormat = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'MMM D, YYYY';
|
|
204
|
+
return moment_default()(date, 'YYYYMMDD').format(strFormat);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* DICOM Time is stored as HHmmss.SSS, where:
|
|
209
|
+
* HH 24 hour time:
|
|
210
|
+
* m mm 0..59 Minutes
|
|
211
|
+
* s ss 0..59 Seconds
|
|
212
|
+
* S SS SSS 0..999 Fractional seconds
|
|
213
|
+
*
|
|
214
|
+
* Goal: '24:12:12'
|
|
215
|
+
*
|
|
216
|
+
* @param {*} time
|
|
217
|
+
* @param {string} strFormat
|
|
218
|
+
* @returns {string} formatted name.
|
|
219
|
+
*/
|
|
220
|
+
function formatDICOMTime(time) {
|
|
221
|
+
let strFormat = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'HH:mm:ss';
|
|
222
|
+
return moment_default()(time, 'HH:mm:ss').format(strFormat);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Formats a patient name for display purposes
|
|
227
|
+
*
|
|
228
|
+
* @param {string} name
|
|
229
|
+
* @returns {string} formatted name.
|
|
230
|
+
*/
|
|
231
|
+
function formatPN(name) {
|
|
232
|
+
if (!name) {
|
|
233
|
+
return '';
|
|
234
|
+
}
|
|
235
|
+
const cleaned = name.split('^').filter(s => !!s).join(', ').trim();
|
|
236
|
+
return cleaned === ',' || cleaned === '' ? '' : cleaned;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Gets compression type
|
|
241
|
+
*
|
|
242
|
+
* @param {number} imageId
|
|
243
|
+
* @returns {string} comrpession type.
|
|
244
|
+
*/
|
|
245
|
+
function getCompression(imageId) {
|
|
246
|
+
const generalImageModule = metaData.get('generalImageModule', imageId) || {};
|
|
247
|
+
const {
|
|
248
|
+
lossyImageCompression,
|
|
249
|
+
lossyImageCompressionRatio,
|
|
250
|
+
lossyImageCompressionMethod
|
|
251
|
+
} = generalImageModule;
|
|
252
|
+
if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') {
|
|
253
|
+
const compressionMethod = lossyImageCompressionMethod || 'Lossy: ';
|
|
254
|
+
const compressionRatio = formatNumberPrecision(lossyImageCompressionRatio, 2);
|
|
255
|
+
return compressionMethod + compressionRatio + ' : 1';
|
|
256
|
+
}
|
|
257
|
+
return 'Lossless / Uncompressed';
|
|
258
|
+
}
|
|
259
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.css
|
|
260
|
+
// extracted by mini-css-extract-plugin
|
|
261
|
+
|
|
262
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
const EPSILON = 1e-4;
|
|
271
|
+
/**
|
|
272
|
+
* Window Level / Center Overlay item
|
|
273
|
+
*/
|
|
274
|
+
function VOIOverlayItem(_ref) {
|
|
275
|
+
let {
|
|
276
|
+
voi,
|
|
277
|
+
customization
|
|
278
|
+
} = _ref;
|
|
279
|
+
const {
|
|
280
|
+
windowWidth,
|
|
281
|
+
windowCenter
|
|
282
|
+
} = voi;
|
|
283
|
+
if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
287
|
+
className: "overlay-item flex flex-row",
|
|
288
|
+
style: {
|
|
289
|
+
color: customization && customization.color || undefined
|
|
290
|
+
}
|
|
291
|
+
}, /*#__PURE__*/react.createElement("span", {
|
|
292
|
+
className: "mr-1 shrink-0"
|
|
293
|
+
}, "W:"), /*#__PURE__*/react.createElement("span", {
|
|
294
|
+
className: "ml-1 mr-2 font-light shrink-0"
|
|
295
|
+
}, windowWidth.toFixed(0)), /*#__PURE__*/react.createElement("span", {
|
|
296
|
+
className: "mr-1 shrink-0"
|
|
297
|
+
}, "L:"), /*#__PURE__*/react.createElement("span", {
|
|
298
|
+
className: "ml-1 font-light shrink-0"
|
|
299
|
+
}, windowCenter.toFixed(0)));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Zoom Level Overlay item
|
|
304
|
+
*/
|
|
305
|
+
function ZoomOverlayItem(_ref2) {
|
|
306
|
+
let {
|
|
307
|
+
scale,
|
|
308
|
+
customization
|
|
309
|
+
} = _ref2;
|
|
310
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
311
|
+
className: "overlay-item flex flex-row",
|
|
312
|
+
style: {
|
|
313
|
+
color: customization && customization.color || undefined
|
|
314
|
+
}
|
|
315
|
+
}, /*#__PURE__*/react.createElement("span", {
|
|
316
|
+
className: "mr-1 shrink-0"
|
|
317
|
+
}, "Zoom:"), /*#__PURE__*/react.createElement("span", {
|
|
318
|
+
className: "font-light"
|
|
319
|
+
}, scale.toFixed(2), "x"));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Instance Number Overlay Item
|
|
324
|
+
*/
|
|
325
|
+
function InstanceNumberOverlayItem(_ref3) {
|
|
326
|
+
let {
|
|
327
|
+
instanceNumber,
|
|
328
|
+
imageSliceData,
|
|
329
|
+
customization
|
|
330
|
+
} = _ref3;
|
|
331
|
+
const {
|
|
332
|
+
imageIndex,
|
|
333
|
+
numberOfSlices
|
|
334
|
+
} = imageSliceData;
|
|
335
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
336
|
+
className: "overlay-item flex flex-row",
|
|
337
|
+
style: {
|
|
338
|
+
color: customization && customization.color || undefined
|
|
339
|
+
}
|
|
340
|
+
}, /*#__PURE__*/react.createElement("span", {
|
|
341
|
+
className: "mr-1 shrink-0"
|
|
342
|
+
}, "I:"), /*#__PURE__*/react.createElement("span", {
|
|
343
|
+
className: "font-light"
|
|
344
|
+
}, instanceNumber !== undefined && instanceNumber !== null ? `${instanceNumber} (${imageIndex + 1}/${numberOfSlices})` : `${imageIndex + 1}/${numberOfSlices}`));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Customizable Viewport Overlay
|
|
349
|
+
*/
|
|
350
|
+
function CustomizableViewportOverlay(_ref4) {
|
|
351
|
+
let {
|
|
352
|
+
element,
|
|
353
|
+
viewportData,
|
|
354
|
+
imageSliceData,
|
|
355
|
+
viewportIndex,
|
|
356
|
+
servicesManager
|
|
357
|
+
} = _ref4;
|
|
358
|
+
const {
|
|
359
|
+
toolbarService,
|
|
360
|
+
cornerstoneViewportService,
|
|
361
|
+
customizationService
|
|
362
|
+
} = servicesManager.services;
|
|
363
|
+
const [voi, setVOI] = (0,react.useState)({
|
|
364
|
+
windowCenter: null,
|
|
365
|
+
windowWidth: null
|
|
366
|
+
});
|
|
367
|
+
const [scale, setScale] = (0,react.useState)(1);
|
|
368
|
+
const [activeTools, setActiveTools] = (0,react.useState)([]);
|
|
369
|
+
const {
|
|
370
|
+
imageIndex
|
|
371
|
+
} = imageSliceData;
|
|
372
|
+
const topLeftCustomization = customizationService.getModeCustomization('cornerstoneOverlayTopLeft');
|
|
373
|
+
const topRightCustomization = customizationService.getModeCustomization('cornerstoneOverlayTopRight');
|
|
374
|
+
const bottomLeftCustomization = customizationService.getModeCustomization('cornerstoneOverlayBottomLeft');
|
|
375
|
+
const bottomRightCustomization = customizationService.getModeCustomization('cornerstoneOverlayBottomRight');
|
|
376
|
+
const instance = (0,react.useMemo)(() => {
|
|
377
|
+
if (viewportData != null) {
|
|
378
|
+
return _getViewportInstance(viewportData, imageIndex);
|
|
379
|
+
} else {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}, [viewportData, imageIndex]);
|
|
383
|
+
const instanceNumber = (0,react.useMemo)(() => {
|
|
384
|
+
if (viewportData != null) {
|
|
385
|
+
return _getInstanceNumber(viewportData, viewportIndex, imageIndex, cornerstoneViewportService);
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}, [viewportData, viewportIndex, imageIndex, cornerstoneViewportService]);
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Initial toolbar state
|
|
392
|
+
*/
|
|
393
|
+
(0,react.useEffect)(() => {
|
|
394
|
+
setActiveTools(toolbarService.getActiveTools());
|
|
395
|
+
}, []);
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Updating the VOI when the viewport changes its voi
|
|
399
|
+
*/
|
|
400
|
+
(0,react.useEffect)(() => {
|
|
401
|
+
const updateVOI = eventDetail => {
|
|
402
|
+
const {
|
|
403
|
+
range
|
|
404
|
+
} = eventDetail.detail;
|
|
405
|
+
if (!range) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const {
|
|
409
|
+
lower,
|
|
410
|
+
upper
|
|
411
|
+
} = range;
|
|
412
|
+
const {
|
|
413
|
+
windowWidth,
|
|
414
|
+
windowCenter
|
|
415
|
+
} = dist_esm.utilities.windowLevel.toWindowLevel(lower, upper);
|
|
416
|
+
setVOI({
|
|
417
|
+
windowCenter,
|
|
418
|
+
windowWidth
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
element.addEventListener(dist_esm.Enums.Events.VOI_MODIFIED, updateVOI);
|
|
422
|
+
return () => {
|
|
423
|
+
element.removeEventListener(dist_esm.Enums.Events.VOI_MODIFIED, updateVOI);
|
|
424
|
+
};
|
|
425
|
+
}, [viewportIndex, viewportData, voi, element]);
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Updating the scale when the viewport changes its zoom
|
|
429
|
+
*/
|
|
430
|
+
(0,react.useEffect)(() => {
|
|
431
|
+
const updateScale = eventDetail => {
|
|
432
|
+
const {
|
|
433
|
+
previousCamera,
|
|
434
|
+
camera
|
|
435
|
+
} = eventDetail.detail;
|
|
436
|
+
if (previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale) {
|
|
437
|
+
const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex(viewportIndex);
|
|
438
|
+
if (!viewport) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const imageData = viewport.getImageData();
|
|
442
|
+
if (!imageData) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (camera.scale) {
|
|
446
|
+
setScale(camera.scale);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const {
|
|
450
|
+
spacing
|
|
451
|
+
} = imageData;
|
|
452
|
+
// convert parallel scale to scale
|
|
453
|
+
const scale = element.clientHeight * spacing[0] * 0.5 / camera.parallelScale;
|
|
454
|
+
setScale(scale);
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
element.addEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, updateScale);
|
|
458
|
+
return () => {
|
|
459
|
+
element.removeEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, updateScale);
|
|
460
|
+
};
|
|
461
|
+
}, [viewportIndex, viewportData, cornerstoneViewportService, element]);
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Updating the active tools when the toolbar changes
|
|
465
|
+
*/
|
|
466
|
+
// Todo: this should act on the toolGroups instead of the toolbar state
|
|
467
|
+
(0,react.useEffect)(() => {
|
|
468
|
+
const {
|
|
469
|
+
unsubscribe
|
|
470
|
+
} = toolbarService.subscribe(toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, () => {
|
|
471
|
+
setActiveTools(toolbarService.getActiveTools());
|
|
472
|
+
});
|
|
473
|
+
return () => {
|
|
474
|
+
unsubscribe();
|
|
475
|
+
};
|
|
476
|
+
}, [toolbarService]);
|
|
477
|
+
const _renderOverlayItem = (0,react.useCallback)(item => {
|
|
478
|
+
const overlayItemProps = {
|
|
479
|
+
element,
|
|
480
|
+
viewportData,
|
|
481
|
+
imageSliceData,
|
|
482
|
+
viewportIndex,
|
|
483
|
+
servicesManager,
|
|
484
|
+
customization: item,
|
|
485
|
+
formatters: {
|
|
486
|
+
formatPN: formatPN,
|
|
487
|
+
formatDate: formatDICOMDate,
|
|
488
|
+
formatTime: formatDICOMTime,
|
|
489
|
+
formatNumberPrecision: formatNumberPrecision
|
|
490
|
+
},
|
|
491
|
+
instance,
|
|
492
|
+
// calculated
|
|
493
|
+
voi,
|
|
494
|
+
scale,
|
|
495
|
+
instanceNumber
|
|
496
|
+
};
|
|
497
|
+
if (item.customizationType === 'ohif.overlayItem.windowLevel') {
|
|
498
|
+
return /*#__PURE__*/react.createElement(VOIOverlayItem, overlayItemProps);
|
|
499
|
+
} else if (item.customizationType === 'ohif.overlayItem.zoomLevel') {
|
|
500
|
+
return /*#__PURE__*/react.createElement(ZoomOverlayItem, overlayItemProps);
|
|
501
|
+
} else if (item.customizationType === 'ohif.overlayItem.instanceNumber') {
|
|
502
|
+
return /*#__PURE__*/react.createElement(InstanceNumberOverlayItem, overlayItemProps);
|
|
503
|
+
} else {
|
|
504
|
+
const renderItem = customizationService.transform(item);
|
|
505
|
+
if (typeof renderItem.content === 'function') {
|
|
506
|
+
return renderItem.content(overlayItemProps);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}, [element, viewportData, imageSliceData, viewportIndex, servicesManager, customizationService, instance, voi, scale, instanceNumber]);
|
|
510
|
+
const getTopLeftContent = (0,react.useCallback)(() => {
|
|
511
|
+
const items = topLeftCustomization?.items || [{
|
|
512
|
+
id: 'WindowLevel',
|
|
513
|
+
customizationType: 'ohif.overlayItem.windowLevel'
|
|
514
|
+
}];
|
|
515
|
+
return /*#__PURE__*/react.createElement(react.Fragment, null, items.map((item, i) => /*#__PURE__*/react.createElement("div", {
|
|
516
|
+
key: `topLeftOverlayItem_${i}`
|
|
517
|
+
}, _renderOverlayItem(item))));
|
|
518
|
+
}, [topLeftCustomization, _renderOverlayItem]);
|
|
519
|
+
const getTopRightContent = (0,react.useCallback)(() => {
|
|
520
|
+
const items = topRightCustomization?.items || [{
|
|
521
|
+
id: 'InstanceNmber',
|
|
522
|
+
customizationType: 'ohif.overlayItem.instanceNumber'
|
|
523
|
+
}];
|
|
524
|
+
return /*#__PURE__*/react.createElement(react.Fragment, null, items.map((item, i) => /*#__PURE__*/react.createElement("div", {
|
|
525
|
+
key: `topRightOverlayItem_${i}`
|
|
526
|
+
}, _renderOverlayItem(item))));
|
|
527
|
+
}, [topRightCustomization, _renderOverlayItem]);
|
|
528
|
+
const getBottomLeftContent = (0,react.useCallback)(() => {
|
|
529
|
+
const items = bottomLeftCustomization?.items || [];
|
|
530
|
+
return /*#__PURE__*/react.createElement(react.Fragment, null, items.map((item, i) => /*#__PURE__*/react.createElement("div", {
|
|
531
|
+
key: `bottomLeftOverlayItem_${i}`
|
|
532
|
+
}, _renderOverlayItem(item))));
|
|
533
|
+
}, [bottomLeftCustomization, _renderOverlayItem]);
|
|
534
|
+
const getBottomRightContent = (0,react.useCallback)(() => {
|
|
535
|
+
const items = bottomRightCustomization?.items || [];
|
|
536
|
+
return /*#__PURE__*/react.createElement(react.Fragment, null, items.map((item, i) => /*#__PURE__*/react.createElement("div", {
|
|
537
|
+
key: `bottomRightOverlayItem_${i}`
|
|
538
|
+
}, _renderOverlayItem(item))));
|
|
539
|
+
}, [bottomRightCustomization, _renderOverlayItem]);
|
|
540
|
+
return /*#__PURE__*/react.createElement(ui_src/* ViewportOverlay */.No, {
|
|
541
|
+
topLeft: getTopLeftContent(),
|
|
542
|
+
topRight: getTopRightContent(),
|
|
543
|
+
bottomLeft: getBottomLeftContent(),
|
|
544
|
+
bottomRight: getBottomRightContent()
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
function _getViewportInstance(viewportData, imageIndex) {
|
|
548
|
+
let imageId = null;
|
|
549
|
+
if (viewportData.viewportType === dist_esm.Enums.ViewportType.STACK) {
|
|
550
|
+
imageId = viewportData.data.imageIds[imageIndex];
|
|
551
|
+
} else if (viewportData.viewportType === dist_esm.Enums.ViewportType.ORTHOGRAPHIC) {
|
|
552
|
+
const volumes = viewportData.volumes;
|
|
553
|
+
if (volumes && volumes.length == 1) {
|
|
554
|
+
const volume = volumes[0];
|
|
555
|
+
imageId = volume.imageIds[imageIndex];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return imageId ? dist_esm.metaData.get('instance', imageId) || {} : {};
|
|
559
|
+
}
|
|
560
|
+
function _getInstanceNumber(viewportData, viewportIndex, imageIndex, cornerstoneViewportService) {
|
|
561
|
+
let instanceNumber;
|
|
562
|
+
if (viewportData.viewportType === dist_esm.Enums.ViewportType.STACK) {
|
|
563
|
+
instanceNumber = _getInstanceNumberFromStack(viewportData, imageIndex);
|
|
564
|
+
if (!instanceNumber && instanceNumber !== 0) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
} else if (viewportData.viewportType === dist_esm.Enums.ViewportType.ORTHOGRAPHIC) {
|
|
568
|
+
instanceNumber = _getInstanceNumberFromVolume(viewportData, imageIndex, viewportIndex, cornerstoneViewportService);
|
|
569
|
+
}
|
|
570
|
+
return instanceNumber;
|
|
571
|
+
}
|
|
572
|
+
function _getInstanceNumberFromStack(viewportData, imageIndex) {
|
|
573
|
+
const imageIds = viewportData.data.imageIds;
|
|
574
|
+
const imageId = imageIds[imageIndex];
|
|
575
|
+
if (!imageId) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const generalImageModule = dist_esm.metaData.get('generalImageModule', imageId) || {};
|
|
579
|
+
const {
|
|
580
|
+
instanceNumber
|
|
581
|
+
} = generalImageModule;
|
|
582
|
+
const stackSize = imageIds.length;
|
|
583
|
+
if (stackSize <= 1) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
return parseInt(instanceNumber);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Since volume viewports can be in any view direction, they can render
|
|
590
|
+
// a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber
|
|
591
|
+
// Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber
|
|
592
|
+
function _getInstanceNumberFromVolume(viewportData, imageIndex, viewportIndex, cornerstoneViewportService) {
|
|
593
|
+
const volumes = viewportData.volumes;
|
|
594
|
+
|
|
595
|
+
// Todo: support fusion of acquisition plane which has instanceNumber
|
|
596
|
+
if (!volumes || volumes.length > 1) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const volume = volumes[0];
|
|
600
|
+
const {
|
|
601
|
+
direction,
|
|
602
|
+
imageIds
|
|
603
|
+
} = volume;
|
|
604
|
+
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex(viewportIndex);
|
|
605
|
+
if (!cornerstoneViewport) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const camera = cornerstoneViewport.getCamera();
|
|
609
|
+
const {
|
|
610
|
+
viewPlaneNormal
|
|
611
|
+
} = camera;
|
|
612
|
+
// checking if camera is looking at the acquisition plane (defined by the direction on the volume)
|
|
613
|
+
|
|
614
|
+
const scanAxisNormal = direction.slice(6, 9);
|
|
615
|
+
|
|
616
|
+
// check if viewPlaneNormal is parallel to scanAxisNormal
|
|
617
|
+
const cross = gl_matrix_esm/* vec3.cross */.R3.cross(gl_matrix_esm/* vec3.create */.R3.create(), viewPlaneNormal, scanAxisNormal);
|
|
618
|
+
const isAcquisitionPlane = gl_matrix_esm/* vec3.length */.R3.length(cross) < EPSILON;
|
|
619
|
+
if (isAcquisitionPlane) {
|
|
620
|
+
const imageId = imageIds[imageIndex];
|
|
621
|
+
if (!imageId) {
|
|
622
|
+
return {};
|
|
623
|
+
}
|
|
624
|
+
const {
|
|
625
|
+
instanceNumber
|
|
626
|
+
} = dist_esm.metaData.get('generalImageModule', imageId) || {};
|
|
627
|
+
return parseInt(instanceNumber);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
CustomizableViewportOverlay.propTypes = {
|
|
631
|
+
viewportData: (prop_types_default()).object,
|
|
632
|
+
imageIndex: (prop_types_default()).number,
|
|
633
|
+
viewportIndex: (prop_types_default()).number
|
|
634
|
+
};
|
|
635
|
+
/* harmony default export */ const Overlays_CustomizableViewportOverlay = (CustomizableViewportOverlay);
|
|
636
|
+
// EXTERNAL MODULE: ../../../node_modules/classnames/index.js
|
|
637
|
+
var classnames = __webpack_require__(40841);
|
|
638
|
+
var classnames_default = /*#__PURE__*/__webpack_require__.n(classnames);
|
|
639
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.css
|
|
640
|
+
// extracted by mini-css-extract-plugin
|
|
641
|
+
|
|
642
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
const {
|
|
651
|
+
getOrientationStringLPS,
|
|
652
|
+
invertOrientationStringLPS
|
|
653
|
+
} = esm.utilities.orientation;
|
|
654
|
+
function ViewportOrientationMarkers(_ref) {
|
|
655
|
+
let {
|
|
656
|
+
element,
|
|
657
|
+
viewportData,
|
|
658
|
+
imageSliceData,
|
|
659
|
+
viewportIndex,
|
|
660
|
+
servicesManager,
|
|
661
|
+
orientationMarkers = ['top', 'left']
|
|
662
|
+
} = _ref;
|
|
663
|
+
// Rotation is in degrees
|
|
664
|
+
const [rotation, setRotation] = (0,react.useState)(0);
|
|
665
|
+
const [flipHorizontal, setFlipHorizontal] = (0,react.useState)(false);
|
|
666
|
+
const [flipVertical, setFlipVertical] = (0,react.useState)(false);
|
|
667
|
+
const {
|
|
668
|
+
cornerstoneViewportService
|
|
669
|
+
} = servicesManager.services;
|
|
670
|
+
(0,react.useEffect)(() => {
|
|
671
|
+
const cameraModifiedListener = evt => {
|
|
672
|
+
const {
|
|
673
|
+
rotation,
|
|
674
|
+
previousCamera,
|
|
675
|
+
camera
|
|
676
|
+
} = evt.detail;
|
|
677
|
+
if (rotation !== undefined) {
|
|
678
|
+
setRotation(rotation);
|
|
679
|
+
}
|
|
680
|
+
if (camera.flipHorizontal !== undefined && previousCamera.flipHorizontal !== camera.flipHorizontal) {
|
|
681
|
+
setFlipHorizontal(camera.flipHorizontal);
|
|
682
|
+
}
|
|
683
|
+
if (camera.flipVertical !== undefined && previousCamera.flipVertical !== camera.flipVertical) {
|
|
684
|
+
setFlipVertical(camera.flipVertical);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
element.addEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
|
|
688
|
+
return () => {
|
|
689
|
+
element.removeEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
|
|
690
|
+
};
|
|
691
|
+
}, []);
|
|
692
|
+
const markers = (0,react.useMemo)(() => {
|
|
693
|
+
if (!viewportData) {
|
|
694
|
+
return '';
|
|
695
|
+
}
|
|
696
|
+
let rowCosines, columnCosines;
|
|
697
|
+
if (viewportData.viewportType === 'stack') {
|
|
698
|
+
const imageIndex = imageSliceData.imageIndex;
|
|
699
|
+
const imageId = viewportData.data.imageIds?.[imageIndex];
|
|
700
|
+
|
|
701
|
+
// Workaround for below TODO stub
|
|
702
|
+
if (!imageId) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
({
|
|
706
|
+
rowCosines,
|
|
707
|
+
columnCosines
|
|
708
|
+
} = dist_esm.metaData.get('imagePlaneModule', imageId) || {});
|
|
709
|
+
} else {
|
|
710
|
+
if (!element || !(0,dist_esm.getEnabledElement)(element)) {
|
|
711
|
+
return '';
|
|
712
|
+
}
|
|
713
|
+
const {
|
|
714
|
+
viewport
|
|
715
|
+
} = (0,dist_esm.getEnabledElement)(element);
|
|
716
|
+
const {
|
|
717
|
+
viewUp,
|
|
718
|
+
viewPlaneNormal
|
|
719
|
+
} = viewport.getCamera();
|
|
720
|
+
const viewRight = gl_matrix_esm/* vec3.create */.R3.create();
|
|
721
|
+
gl_matrix_esm/* vec3.cross */.R3.cross(viewRight, viewUp, viewPlaneNormal);
|
|
722
|
+
columnCosines = [-viewUp[0], -viewUp[1], -viewUp[2]];
|
|
723
|
+
rowCosines = viewRight;
|
|
724
|
+
}
|
|
725
|
+
if (!rowCosines || !columnCosines || rotation === undefined) {
|
|
726
|
+
return '';
|
|
727
|
+
}
|
|
728
|
+
const markers = _getOrientationMarkers(rowCosines, columnCosines, rotation, flipVertical, flipHorizontal);
|
|
729
|
+
const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
730
|
+
if (!ohifViewport) {
|
|
731
|
+
console.log('ViewportOrientationMarkers::No viewport');
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
const backgroundColor = ohifViewport.getViewportOptions().background;
|
|
735
|
+
|
|
736
|
+
// Todo: probably this can be done in a better way in which we identify bright
|
|
737
|
+
// background
|
|
738
|
+
const isLight = backgroundColor ? dist_esm.utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
|
|
739
|
+
return orientationMarkers.map((m, index) => /*#__PURE__*/react.createElement("div", {
|
|
740
|
+
className: classnames_default()(`${m}-mid orientation-marker`, isLight ? 'text-[#726F7E]' : 'text-[#ccc]'),
|
|
741
|
+
key: `${m}-mid orientation-marker`
|
|
742
|
+
}, /*#__PURE__*/react.createElement("div", {
|
|
743
|
+
className: "orientation-marker-value"
|
|
744
|
+
}, markers[m])));
|
|
745
|
+
}, [viewportData, imageSliceData, rotation, flipVertical, flipHorizontal, orientationMarkers, element]);
|
|
746
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
747
|
+
className: "ViewportOrientationMarkers noselect"
|
|
748
|
+
}, markers);
|
|
749
|
+
}
|
|
750
|
+
ViewportOrientationMarkers.propTypes = {
|
|
751
|
+
percentComplete: (prop_types_default()).number,
|
|
752
|
+
error: (prop_types_default()).object
|
|
753
|
+
};
|
|
754
|
+
ViewportOrientationMarkers.defaultProps = {
|
|
755
|
+
percentComplete: 0,
|
|
756
|
+
error: null
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
*
|
|
761
|
+
* Computes the orientation labels on a Cornerstone-enabled Viewport element
|
|
762
|
+
* when the viewport settings change (e.g. when a horizontal flip or a rotation occurs)
|
|
763
|
+
*
|
|
764
|
+
* @param {*} rowCosines
|
|
765
|
+
* @param {*} columnCosines
|
|
766
|
+
* @param {*} rotation in degrees
|
|
767
|
+
* @returns
|
|
768
|
+
*/
|
|
769
|
+
function _getOrientationMarkers(rowCosines, columnCosines, rotation, flipVertical, flipHorizontal) {
|
|
770
|
+
const rowString = getOrientationStringLPS(rowCosines);
|
|
771
|
+
const columnString = getOrientationStringLPS(columnCosines);
|
|
772
|
+
const oppositeRowString = invertOrientationStringLPS(rowString);
|
|
773
|
+
const oppositeColumnString = invertOrientationStringLPS(columnString);
|
|
774
|
+
const markers = {
|
|
775
|
+
top: oppositeColumnString,
|
|
776
|
+
left: oppositeRowString,
|
|
777
|
+
right: rowString,
|
|
778
|
+
bottom: columnString
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// If any vertical or horizontal flips are applied, change the orientation strings ahead of
|
|
782
|
+
// the rotation applications
|
|
783
|
+
if (flipVertical) {
|
|
784
|
+
markers.top = invertOrientationStringLPS(markers.top);
|
|
785
|
+
markers.bottom = invertOrientationStringLPS(markers.bottom);
|
|
786
|
+
}
|
|
787
|
+
if (flipHorizontal) {
|
|
788
|
+
markers.left = invertOrientationStringLPS(markers.left);
|
|
789
|
+
markers.right = invertOrientationStringLPS(markers.right);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Swap the labels accordingly if the viewport has been rotated
|
|
793
|
+
// This could be done in a more complex way for intermediate rotation values (e.g. 45 degrees)
|
|
794
|
+
if (rotation === 90 || rotation === -270) {
|
|
795
|
+
return {
|
|
796
|
+
top: markers.left,
|
|
797
|
+
left: invertOrientationStringLPS(markers.top),
|
|
798
|
+
right: invertOrientationStringLPS(markers.bottom),
|
|
799
|
+
bottom: markers.right // left
|
|
800
|
+
};
|
|
801
|
+
} else if (rotation === -90 || rotation === 270) {
|
|
802
|
+
return {
|
|
803
|
+
top: invertOrientationStringLPS(markers.left),
|
|
804
|
+
left: markers.top,
|
|
805
|
+
bottom: markers.left,
|
|
806
|
+
right: markers.bottom
|
|
807
|
+
};
|
|
808
|
+
} else if (rotation === 180 || rotation === -180) {
|
|
809
|
+
return {
|
|
810
|
+
top: invertOrientationStringLPS(markers.top),
|
|
811
|
+
left: invertOrientationStringLPS(markers.left),
|
|
812
|
+
bottom: invertOrientationStringLPS(markers.bottom),
|
|
813
|
+
right: invertOrientationStringLPS(markers.right)
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
return markers;
|
|
817
|
+
}
|
|
818
|
+
/* harmony default export */ const Overlays_ViewportOrientationMarkers = (ViewportOrientationMarkers);
|
|
819
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
function ViewportImageSliceLoadingIndicator(_ref) {
|
|
824
|
+
let {
|
|
825
|
+
viewportData,
|
|
826
|
+
element
|
|
827
|
+
} = _ref;
|
|
828
|
+
const [loading, setLoading] = (0,react.useState)(false);
|
|
829
|
+
const [error, setError] = (0,react.useState)(false);
|
|
830
|
+
const loadIndicatorRef = (0,react.useRef)(null);
|
|
831
|
+
const imageIdToBeLoaded = (0,react.useRef)(null);
|
|
832
|
+
const setLoadingState = evt => {
|
|
833
|
+
clearTimeout(loadIndicatorRef.current);
|
|
834
|
+
loadIndicatorRef.current = setTimeout(() => {
|
|
835
|
+
setLoading(true);
|
|
836
|
+
}, 50);
|
|
837
|
+
};
|
|
838
|
+
const setFinishLoadingState = evt => {
|
|
839
|
+
clearTimeout(loadIndicatorRef.current);
|
|
840
|
+
setLoading(false);
|
|
841
|
+
};
|
|
842
|
+
const setErrorState = evt => {
|
|
843
|
+
clearTimeout(loadIndicatorRef.current);
|
|
844
|
+
if (imageIdToBeLoaded.current === evt.detail.imageId) {
|
|
845
|
+
setError(evt.detail.error);
|
|
846
|
+
imageIdToBeLoaded.current = null;
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
(0,react.useEffect)(() => {
|
|
850
|
+
element.addEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState);
|
|
851
|
+
element.addEventListener(dist_esm.Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
|
|
852
|
+
element.addEventListener(dist_esm.Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState);
|
|
853
|
+
return () => {
|
|
854
|
+
element.removeEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState);
|
|
855
|
+
element.removeEventListener(dist_esm.Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState);
|
|
856
|
+
element.removeEventListener(dist_esm.Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
|
|
857
|
+
};
|
|
858
|
+
}, [element, viewportData]);
|
|
859
|
+
if (error) {
|
|
860
|
+
return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
|
|
861
|
+
className: "bg-black opacity-50 absolute h-full w-full top-0 left-0"
|
|
862
|
+
}, /*#__PURE__*/react.createElement("div", {
|
|
863
|
+
className: "flex transparent items-center justify-center w-full h-full"
|
|
864
|
+
}, /*#__PURE__*/react.createElement("p", {
|
|
865
|
+
className: "text-primary-light text-xl font-light"
|
|
866
|
+
}, /*#__PURE__*/react.createElement("h4", null, "Error Loading Image"), /*#__PURE__*/react.createElement("p", null, "An error has occurred."), /*#__PURE__*/react.createElement("p", null, error)))));
|
|
867
|
+
}
|
|
868
|
+
if (loading) {
|
|
869
|
+
return (
|
|
870
|
+
/*#__PURE__*/
|
|
871
|
+
// IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from
|
|
872
|
+
// interacting with the mouse, since scrolling should propagate to the viewport underneath
|
|
873
|
+
react.createElement("div", {
|
|
874
|
+
className: "pointer-events-none bg-black opacity-50 absolute h-full w-full top-0 left-0"
|
|
875
|
+
}, /*#__PURE__*/react.createElement("div", {
|
|
876
|
+
className: "flex transparent items-center justify-center w-full h-full"
|
|
877
|
+
}, /*#__PURE__*/react.createElement("p", {
|
|
878
|
+
className: "text-primary-light text-xl font-light"
|
|
879
|
+
}, "Loading...")))
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
ViewportImageSliceLoadingIndicator.propTypes = {
|
|
885
|
+
percentComplete: (prop_types_default()).number,
|
|
886
|
+
error: (prop_types_default()).object,
|
|
887
|
+
element: (prop_types_default()).object
|
|
888
|
+
};
|
|
889
|
+
ViewportImageSliceLoadingIndicator.defaultProps = {
|
|
890
|
+
percentComplete: 0,
|
|
891
|
+
error: null
|
|
892
|
+
};
|
|
893
|
+
/* harmony default export */ const Overlays_ViewportImageSliceLoadingIndicator = (ViewportImageSliceLoadingIndicator);
|
|
894
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
function CornerstoneOverlays(props) {
|
|
901
|
+
const {
|
|
902
|
+
viewportIndex,
|
|
903
|
+
element,
|
|
904
|
+
scrollbarHeight,
|
|
905
|
+
servicesManager
|
|
906
|
+
} = props;
|
|
907
|
+
const {
|
|
908
|
+
cornerstoneViewportService
|
|
909
|
+
} = servicesManager.services;
|
|
910
|
+
const [imageSliceData, setImageSliceData] = (0,react.useState)({
|
|
911
|
+
imageIndex: 0,
|
|
912
|
+
numberOfSlices: 0
|
|
913
|
+
});
|
|
914
|
+
const [viewportData, setViewportData] = (0,react.useState)(null);
|
|
915
|
+
(0,react.useEffect)(() => {
|
|
916
|
+
const {
|
|
917
|
+
unsubscribe
|
|
918
|
+
} = cornerstoneViewportService.subscribe(cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, props => {
|
|
919
|
+
if (props.viewportIndex !== viewportIndex) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
setViewportData(props.viewportData);
|
|
923
|
+
});
|
|
924
|
+
return () => {
|
|
925
|
+
unsubscribe();
|
|
926
|
+
};
|
|
927
|
+
}, [viewportIndex]);
|
|
928
|
+
if (!element) {
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
if (viewportData) {
|
|
932
|
+
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
933
|
+
if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
938
|
+
className: "noselect"
|
|
939
|
+
}, /*#__PURE__*/react.createElement(ViewportImageScrollbar, {
|
|
940
|
+
viewportIndex: viewportIndex,
|
|
941
|
+
viewportData: viewportData,
|
|
942
|
+
element: element,
|
|
943
|
+
imageSliceData: imageSliceData,
|
|
944
|
+
setImageSliceData: setImageSliceData,
|
|
945
|
+
scrollbarHeight: scrollbarHeight,
|
|
946
|
+
servicesManager: servicesManager
|
|
947
|
+
}), /*#__PURE__*/react.createElement(Overlays_CustomizableViewportOverlay, {
|
|
948
|
+
imageSliceData: imageSliceData,
|
|
949
|
+
viewportData: viewportData,
|
|
950
|
+
viewportIndex: viewportIndex,
|
|
951
|
+
servicesManager: servicesManager,
|
|
952
|
+
element: element
|
|
953
|
+
}), /*#__PURE__*/react.createElement(Overlays_ViewportImageSliceLoadingIndicator, {
|
|
954
|
+
viewportData: viewportData,
|
|
955
|
+
element: element
|
|
956
|
+
}), /*#__PURE__*/react.createElement(Overlays_ViewportOrientationMarkers, {
|
|
957
|
+
imageSliceData: imageSliceData,
|
|
958
|
+
element: element,
|
|
959
|
+
viewportData: viewportData,
|
|
960
|
+
servicesManager: servicesManager,
|
|
961
|
+
viewportIndex: viewportIndex
|
|
962
|
+
}));
|
|
963
|
+
}
|
|
964
|
+
/* harmony default export */ const Overlays_CornerstoneOverlays = (CornerstoneOverlays);
|
|
965
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js
|
|
966
|
+
var getSOPInstanceAttributes = __webpack_require__(63130);
|
|
967
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
const STACK = 'stack';
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Caches the jump to measurement operation, so that if display set is shown,
|
|
983
|
+
* it can jump to the measurement.
|
|
984
|
+
*/
|
|
985
|
+
let cacheJumpToMeasurementEvent;
|
|
986
|
+
function areEqual(prevProps, nextProps) {
|
|
987
|
+
if (nextProps.needsRerendering) {
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
if (prevProps.displaySets.length !== nextProps.displaySets.length) {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) {
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
if (prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType) {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
const prevDisplaySets = prevProps.displaySets;
|
|
1003
|
+
const nextDisplaySets = nextProps.displaySets;
|
|
1004
|
+
if (prevDisplaySets.length !== nextDisplaySets.length) {
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
for (let i = 0; i < prevDisplaySets.length; i++) {
|
|
1008
|
+
const prevDisplaySet = prevDisplaySets[i];
|
|
1009
|
+
const foundDisplaySet = nextDisplaySets.find(nextDisplaySet => nextDisplaySet.displaySetInstanceUID === prevDisplaySet.displaySetInstanceUID);
|
|
1010
|
+
if (!foundDisplaySet) {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// check they contain the same image
|
|
1015
|
+
if (foundDisplaySet.images?.length !== prevDisplaySet.images?.length) {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// check if their imageIds are the same
|
|
1020
|
+
if (foundDisplaySet.images?.length) {
|
|
1021
|
+
for (let j = 0; j < foundDisplaySet.images.length; j++) {
|
|
1022
|
+
if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Todo: This should be done with expose of internal API similar to react-vtkjs-viewport
|
|
1032
|
+
// Then we don't need to worry about the re-renders if the props change.
|
|
1033
|
+
const OHIFCornerstoneViewport = /*#__PURE__*/react.memo(props => {
|
|
1034
|
+
const {
|
|
1035
|
+
viewportIndex,
|
|
1036
|
+
displaySets,
|
|
1037
|
+
dataSource,
|
|
1038
|
+
viewportOptions,
|
|
1039
|
+
displaySetOptions,
|
|
1040
|
+
servicesManager,
|
|
1041
|
+
onElementEnabled,
|
|
1042
|
+
onElementDisabled,
|
|
1043
|
+
// Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
|
|
1044
|
+
// of the imageData in the OHIFCornerstoneViewport. This prop is used
|
|
1045
|
+
// to set the initial state of the viewport's first image to render
|
|
1046
|
+
initialImageIndex
|
|
1047
|
+
} = props;
|
|
1048
|
+
const [scrollbarHeight, setScrollbarHeight] = (0,react.useState)('100px');
|
|
1049
|
+
const [{
|
|
1050
|
+
isCineEnabled,
|
|
1051
|
+
cines
|
|
1052
|
+
}, cineService] = (0,ui_src/* useCine */.vQ)();
|
|
1053
|
+
const [{
|
|
1054
|
+
activeViewportIndex
|
|
1055
|
+
}] = (0,ui_src/* useViewportGrid */.O_)();
|
|
1056
|
+
const [enabledVPElement, setEnabledVPElement] = (0,react.useState)(null);
|
|
1057
|
+
const elementRef = (0,react.useRef)();
|
|
1058
|
+
const {
|
|
1059
|
+
measurementService,
|
|
1060
|
+
displaySetService,
|
|
1061
|
+
toolbarService,
|
|
1062
|
+
toolGroupService,
|
|
1063
|
+
syncGroupService,
|
|
1064
|
+
cornerstoneViewportService,
|
|
1065
|
+
cornerstoneCacheService,
|
|
1066
|
+
viewportGridService,
|
|
1067
|
+
stateSyncService
|
|
1068
|
+
} = servicesManager.services;
|
|
1069
|
+
const [viewportDialogState] = (0,ui_src/* useViewportDialog */.en)();
|
|
1070
|
+
const cineHandler = () => {
|
|
1071
|
+
if (!cines || !cines[viewportIndex] || !enabledVPElement) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const cine = cines[viewportIndex];
|
|
1075
|
+
const isPlaying = cine.isPlaying || false;
|
|
1076
|
+
const frameRate = cine.frameRate || 24;
|
|
1077
|
+
const validFrameRate = Math.max(frameRate, 1);
|
|
1078
|
+
if (isPlaying) {
|
|
1079
|
+
cineService.playClip(enabledVPElement, {
|
|
1080
|
+
framesPerSecond: validFrameRate
|
|
1081
|
+
});
|
|
1082
|
+
} else {
|
|
1083
|
+
cineService.stopClip(enabledVPElement);
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
(0,react.useEffect)(() => {
|
|
1087
|
+
dist_esm.eventTarget.addEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler);
|
|
1088
|
+
return () => {
|
|
1089
|
+
cineService.setCine({
|
|
1090
|
+
id: viewportIndex,
|
|
1091
|
+
isPlaying: false
|
|
1092
|
+
});
|
|
1093
|
+
dist_esm.eventTarget.removeEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_NEW_STACK, cineHandler);
|
|
1094
|
+
};
|
|
1095
|
+
}, [enabledVPElement]);
|
|
1096
|
+
(0,react.useEffect)(() => {
|
|
1097
|
+
if (!cines || !cines[viewportIndex] || !enabledVPElement) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
cineHandler();
|
|
1101
|
+
return () => {
|
|
1102
|
+
if (enabledVPElement && cines?.[viewportIndex]?.isPlaying) {
|
|
1103
|
+
cineService.stopClip(enabledVPElement);
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
}, [cines, viewportIndex, cineService, enabledVPElement, cineHandler]);
|
|
1107
|
+
const cine = cines[viewportIndex];
|
|
1108
|
+
const isPlaying = cine && cine.isPlaying || false;
|
|
1109
|
+
const handleCineClose = () => {
|
|
1110
|
+
toolbarService.recordInteraction({
|
|
1111
|
+
groupId: 'MoreTools',
|
|
1112
|
+
itemId: 'cine',
|
|
1113
|
+
interactionType: 'toggle',
|
|
1114
|
+
commands: [{
|
|
1115
|
+
commandName: 'toggleCine',
|
|
1116
|
+
commandOptions: {},
|
|
1117
|
+
context: 'CORNERSTONE'
|
|
1118
|
+
}]
|
|
1119
|
+
});
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
// useCallback for scroll bar height calculation
|
|
1123
|
+
const setImageScrollBarHeight = (0,react.useCallback)(() => {
|
|
1124
|
+
const scrollbarHeight = `${elementRef.current.clientHeight - 20}px`;
|
|
1125
|
+
setScrollbarHeight(scrollbarHeight);
|
|
1126
|
+
}, [elementRef]);
|
|
1127
|
+
|
|
1128
|
+
// useCallback for onResize
|
|
1129
|
+
const onResize = (0,react.useCallback)(() => {
|
|
1130
|
+
if (elementRef.current) {
|
|
1131
|
+
cornerstoneViewportService.resize();
|
|
1132
|
+
setImageScrollBarHeight();
|
|
1133
|
+
}
|
|
1134
|
+
}, [elementRef]);
|
|
1135
|
+
const storePresentation = () => {
|
|
1136
|
+
const currentPresentation = cornerstoneViewportService.getPresentation(viewportIndex);
|
|
1137
|
+
if (!currentPresentation || !currentPresentation.presentationIds) return;
|
|
1138
|
+
const {
|
|
1139
|
+
lutPresentationStore,
|
|
1140
|
+
positionPresentationStore
|
|
1141
|
+
} = stateSyncService.getState();
|
|
1142
|
+
const {
|
|
1143
|
+
presentationIds
|
|
1144
|
+
} = currentPresentation;
|
|
1145
|
+
const {
|
|
1146
|
+
lutPresentationId,
|
|
1147
|
+
positionPresentationId
|
|
1148
|
+
} = presentationIds || {};
|
|
1149
|
+
const storeState = {};
|
|
1150
|
+
if (lutPresentationId) {
|
|
1151
|
+
storeState.lutPresentationStore = {
|
|
1152
|
+
...lutPresentationStore,
|
|
1153
|
+
[lutPresentationId]: currentPresentation
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
if (positionPresentationId) {
|
|
1157
|
+
storeState.positionPresentationStore = {
|
|
1158
|
+
...positionPresentationStore,
|
|
1159
|
+
[positionPresentationId]: currentPresentation
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
stateSyncService.store(storeState);
|
|
1163
|
+
};
|
|
1164
|
+
const cleanUpServices = (0,react.useCallback)(() => {
|
|
1165
|
+
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
1166
|
+
if (!viewportInfo) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const viewportId = viewportInfo.getViewportId();
|
|
1170
|
+
const renderingEngineId = viewportInfo.getRenderingEngineId();
|
|
1171
|
+
const syncGroups = viewportInfo.getSyncGroups();
|
|
1172
|
+
toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId);
|
|
1173
|
+
syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroups);
|
|
1174
|
+
}, [viewportIndex, viewportOptions.viewportId]);
|
|
1175
|
+
const elementEnabledHandler = (0,react.useCallback)(evt => {
|
|
1176
|
+
// check this is this element reference and return early if doesn't match
|
|
1177
|
+
if (evt.detail.element !== elementRef.current) {
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const {
|
|
1181
|
+
viewportId,
|
|
1182
|
+
element
|
|
1183
|
+
} = evt.detail;
|
|
1184
|
+
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
|
1185
|
+
const viewportIndex = viewportInfo.getViewportIndex();
|
|
1186
|
+
(0,state/* setEnabledElement */.Yc)(viewportIndex, element);
|
|
1187
|
+
setEnabledVPElement(element);
|
|
1188
|
+
const renderingEngineId = viewportInfo.getRenderingEngineId();
|
|
1189
|
+
const toolGroupId = viewportInfo.getToolGroupId();
|
|
1190
|
+
const syncGroups = viewportInfo.getSyncGroups();
|
|
1191
|
+
toolGroupService.addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId);
|
|
1192
|
+
syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups);
|
|
1193
|
+
if (onElementEnabled) {
|
|
1194
|
+
onElementEnabled(evt);
|
|
1195
|
+
}
|
|
1196
|
+
}, [viewportIndex, onElementEnabled, toolGroupService]);
|
|
1197
|
+
|
|
1198
|
+
// disable the element upon unmounting
|
|
1199
|
+
(0,react.useEffect)(() => {
|
|
1200
|
+
cornerstoneViewportService.enableViewport(viewportIndex, viewportOptions, elementRef.current);
|
|
1201
|
+
dist_esm.eventTarget.addEventListener(dist_esm.Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
|
|
1202
|
+
setImageScrollBarHeight();
|
|
1203
|
+
return () => {
|
|
1204
|
+
storePresentation();
|
|
1205
|
+
cleanUpServices();
|
|
1206
|
+
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
1207
|
+
cornerstoneViewportService.disableElement(viewportIndex);
|
|
1208
|
+
if (onElementDisabled) {
|
|
1209
|
+
onElementDisabled(viewportInfo);
|
|
1210
|
+
}
|
|
1211
|
+
dist_esm.eventTarget.removeEventListener(dist_esm.Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
|
|
1212
|
+
};
|
|
1213
|
+
}, []);
|
|
1214
|
+
|
|
1215
|
+
// subscribe to displaySet metadata invalidation (updates)
|
|
1216
|
+
// Currently, if the metadata changes we need to re-render the display set
|
|
1217
|
+
// for it to take effect in the viewport. As we deal with scaling in the loading,
|
|
1218
|
+
// we need to remove the old volume from the cache, and let the
|
|
1219
|
+
// viewport to re-add it which will use the new metadata. Otherwise, the
|
|
1220
|
+
// viewport will use the cached volume and the new metadata will not be used.
|
|
1221
|
+
// Note: this approach does not actually end of sending network requests
|
|
1222
|
+
// and it uses the network cache
|
|
1223
|
+
(0,react.useEffect)(() => {
|
|
1224
|
+
const {
|
|
1225
|
+
unsubscribe
|
|
1226
|
+
} = displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, async invalidatedDisplaySetInstanceUID => {
|
|
1227
|
+
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
1228
|
+
if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) {
|
|
1229
|
+
const viewportData = viewportInfo.getViewportData();
|
|
1230
|
+
const newViewportData = await cornerstoneCacheService.invalidateViewportData(viewportData, invalidatedDisplaySetInstanceUID, dataSource, displaySetService);
|
|
1231
|
+
const keepCamera = true;
|
|
1232
|
+
cornerstoneViewportService.updateViewport(viewportIndex, newViewportData, keepCamera);
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
return () => {
|
|
1236
|
+
unsubscribe();
|
|
1237
|
+
};
|
|
1238
|
+
}, [viewportIndex]);
|
|
1239
|
+
(0,react.useEffect)(() => {
|
|
1240
|
+
// handle the default viewportType to be stack
|
|
1241
|
+
if (!viewportOptions.viewportType) {
|
|
1242
|
+
viewportOptions.viewportType = STACK;
|
|
1243
|
+
}
|
|
1244
|
+
const loadViewportData = async () => {
|
|
1245
|
+
const viewportData = await cornerstoneCacheService.createViewportData(displaySets, viewportOptions, dataSource, initialImageIndex);
|
|
1246
|
+
|
|
1247
|
+
// The presentation state will have been stored previously by closing
|
|
1248
|
+
// a viewport. Otherwise, this viewport will be unchanged and the
|
|
1249
|
+
// presentation information will be directly carried over.
|
|
1250
|
+
const {
|
|
1251
|
+
lutPresentationStore,
|
|
1252
|
+
positionPresentationStore
|
|
1253
|
+
} = stateSyncService.getState();
|
|
1254
|
+
const {
|
|
1255
|
+
presentationIds
|
|
1256
|
+
} = viewportOptions;
|
|
1257
|
+
const presentations = {
|
|
1258
|
+
positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId],
|
|
1259
|
+
lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId]
|
|
1260
|
+
};
|
|
1261
|
+
let measurement;
|
|
1262
|
+
if (cacheJumpToMeasurementEvent?.viewportIndex === viewportIndex) {
|
|
1263
|
+
measurement = cacheJumpToMeasurementEvent.measurement;
|
|
1264
|
+
// Delete the position presentation so that viewport navigates direct
|
|
1265
|
+
presentations.positionPresentation = null;
|
|
1266
|
+
cacheJumpToMeasurementEvent = null;
|
|
1267
|
+
}
|
|
1268
|
+
cornerstoneViewportService.setViewportData(viewportIndex, viewportData, viewportOptions, displaySetOptions, presentations);
|
|
1269
|
+
if (measurement) {
|
|
1270
|
+
esm.annotation.selection.setAnnotationSelected(measurement.uid);
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
loadViewportData();
|
|
1274
|
+
}, [viewportOptions, displaySets, dataSource]);
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* There are two scenarios for jump to click
|
|
1278
|
+
* 1. Current viewports contain the displaySet that the annotation was drawn on
|
|
1279
|
+
* 2. Current viewports don't contain the displaySet that the annotation was drawn on
|
|
1280
|
+
* and we need to change the viewports displaySet for jumping.
|
|
1281
|
+
* Since measurement_jump happens via events and listeners, the former case is handled
|
|
1282
|
+
* by the measurement_jump direct callback, but the latter case is handled first by
|
|
1283
|
+
* the viewportGrid to set the correct displaySet on the viewport, AND THEN we check
|
|
1284
|
+
* the cache for jumping to see if there is any jump queued, then we jump to the correct slice.
|
|
1285
|
+
*/
|
|
1286
|
+
(0,react.useEffect)(() => {
|
|
1287
|
+
const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportIndex, displaySets, viewportGridService, cornerstoneViewportService);
|
|
1288
|
+
_checkForCachedJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportIndex, displaySets, viewportGridService, cornerstoneViewportService);
|
|
1289
|
+
return () => {
|
|
1290
|
+
unsubscribeFromJumpToMeasurementEvents();
|
|
1291
|
+
};
|
|
1292
|
+
}, [displaySets, elementRef, viewportIndex]);
|
|
1293
|
+
return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
|
|
1294
|
+
className: "viewport-wrapper"
|
|
1295
|
+
}, /*#__PURE__*/react.createElement(index_esm/* default */.ZP, {
|
|
1296
|
+
handleWidth: true,
|
|
1297
|
+
handleHeight: true,
|
|
1298
|
+
skipOnMount: true // Todo: make these configurable
|
|
1299
|
+
,
|
|
1300
|
+
refreshMode: 'debounce',
|
|
1301
|
+
refreshRate: 200 // transition amount in side panel
|
|
1302
|
+
,
|
|
1303
|
+
onResize: onResize,
|
|
1304
|
+
targetRef: elementRef.current
|
|
1305
|
+
}), /*#__PURE__*/react.createElement("div", {
|
|
1306
|
+
className: "cornerstone-viewport-element",
|
|
1307
|
+
style: {
|
|
1308
|
+
height: '100%',
|
|
1309
|
+
width: '100%'
|
|
1310
|
+
},
|
|
1311
|
+
onContextMenu: e => e.preventDefault(),
|
|
1312
|
+
onMouseDown: e => e.preventDefault(),
|
|
1313
|
+
ref: elementRef
|
|
1314
|
+
}), /*#__PURE__*/react.createElement(Overlays_CornerstoneOverlays, {
|
|
1315
|
+
viewportIndex: viewportIndex,
|
|
1316
|
+
toolBarService: toolbarService,
|
|
1317
|
+
element: elementRef.current,
|
|
1318
|
+
scrollbarHeight: scrollbarHeight,
|
|
1319
|
+
servicesManager: servicesManager
|
|
1320
|
+
}), isCineEnabled && /*#__PURE__*/react.createElement(ui_src/* CinePlayer */.H6, {
|
|
1321
|
+
className: "absolute left-1/2 -translate-x-1/2 bottom-3",
|
|
1322
|
+
isPlaying: isPlaying,
|
|
1323
|
+
onClose: handleCineClose,
|
|
1324
|
+
onPlayPauseChange: isPlaying => cineService.setCine({
|
|
1325
|
+
id: activeViewportIndex,
|
|
1326
|
+
isPlaying
|
|
1327
|
+
}),
|
|
1328
|
+
onFrameRateChange: frameRate => cineService.setCine({
|
|
1329
|
+
id: activeViewportIndex,
|
|
1330
|
+
frameRate
|
|
1331
|
+
})
|
|
1332
|
+
})), /*#__PURE__*/react.createElement("div", {
|
|
1333
|
+
className: "absolute w-full"
|
|
1334
|
+
}, viewportDialogState.viewportIndex === viewportIndex && /*#__PURE__*/react.createElement(ui_src/* Notification */.P_, {
|
|
1335
|
+
id: "viewport-notification",
|
|
1336
|
+
message: viewportDialogState.message,
|
|
1337
|
+
type: viewportDialogState.type,
|
|
1338
|
+
actions: viewportDialogState.actions,
|
|
1339
|
+
onSubmit: viewportDialogState.onSubmit,
|
|
1340
|
+
onOutsideClick: viewportDialogState.onOutsideClick
|
|
1341
|
+
})));
|
|
1342
|
+
}, areEqual);
|
|
1343
|
+
function _subscribeToJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportIndex, displaySets, viewportGridService, cornerstoneViewportService) {
|
|
1344
|
+
const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID);
|
|
1345
|
+
const {
|
|
1346
|
+
unsubscribe
|
|
1347
|
+
} = measurementService.subscribe(src.MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, props => {
|
|
1348
|
+
cacheJumpToMeasurementEvent = props;
|
|
1349
|
+
const {
|
|
1350
|
+
viewportIndex: jumpIndex,
|
|
1351
|
+
measurement,
|
|
1352
|
+
isConsumed
|
|
1353
|
+
} = props;
|
|
1354
|
+
if (!measurement || isConsumed) return;
|
|
1355
|
+
if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
|
|
1356
|
+
// Decide on which viewport should handle this
|
|
1357
|
+
cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIndexToJump(jumpIndex, measurement.displaySetInstanceUID, {
|
|
1358
|
+
referencedImageId: measurement.referencedImageId
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportIndex) {
|
|
1362
|
+
return;
|
|
1363
|
+
}
|
|
1364
|
+
_jumpToMeasurement(measurement, elementRef, viewportIndex, measurementService, displaySetService, viewportGridService, cornerstoneViewportService);
|
|
1365
|
+
});
|
|
1366
|
+
return unsubscribe;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Check if there is a queued jumpToMeasurement event
|
|
1370
|
+
function _checkForCachedJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportIndex, displaySets, viewportGridService, cornerstoneViewportService) {
|
|
1371
|
+
if (!cacheJumpToMeasurementEvent) return;
|
|
1372
|
+
if (cacheJumpToMeasurementEvent.isConsumed) {
|
|
1373
|
+
cacheJumpToMeasurementEvent = null;
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID);
|
|
1377
|
+
if (!displaysUIDs?.length) return;
|
|
1378
|
+
|
|
1379
|
+
// Jump to measurement if the measurement exists
|
|
1380
|
+
const {
|
|
1381
|
+
measurement
|
|
1382
|
+
} = cacheJumpToMeasurementEvent;
|
|
1383
|
+
if (measurement && elementRef) {
|
|
1384
|
+
if (displaysUIDs.includes(measurement?.displaySetInstanceUID)) {
|
|
1385
|
+
_jumpToMeasurement(measurement, elementRef, viewportIndex, measurementService, displaySetService, viewportGridService, cornerstoneViewportService);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
function _jumpToMeasurement(measurement, targetElementRef, viewportIndex, measurementService, displaySetService, viewportGridService, cornerstoneViewportService) {
|
|
1390
|
+
const targetElement = targetElementRef.current;
|
|
1391
|
+
const {
|
|
1392
|
+
displaySetInstanceUID,
|
|
1393
|
+
SOPInstanceUID,
|
|
1394
|
+
frameNumber
|
|
1395
|
+
} = measurement;
|
|
1396
|
+
if (!SOPInstanceUID) {
|
|
1397
|
+
console.warn('cannot jump in a non-acquisition plane measurements yet');
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
|
1401
|
+
|
|
1402
|
+
// Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
|
|
1403
|
+
// to set it properly
|
|
1404
|
+
// setCornerstoneMeasurementActive(measurement);
|
|
1405
|
+
|
|
1406
|
+
viewportGridService.setActiveViewportIndex(viewportIndex);
|
|
1407
|
+
const enabledElement = (0,dist_esm.getEnabledElement)(targetElement);
|
|
1408
|
+
const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex(viewportIndex);
|
|
1409
|
+
if (enabledElement) {
|
|
1410
|
+
// See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
|
|
1411
|
+
const viewport = enabledElement.viewport;
|
|
1412
|
+
let imageIdIndex = 0;
|
|
1413
|
+
let viewportCameraDirectionMatch = true;
|
|
1414
|
+
if (viewport instanceof dist_esm.StackViewport) {
|
|
1415
|
+
const imageIds = viewport.getImageIds();
|
|
1416
|
+
imageIdIndex = imageIds.findIndex(imageId => {
|
|
1417
|
+
const {
|
|
1418
|
+
SOPInstanceUID: aSOPInstanceUID,
|
|
1419
|
+
frameNumber: aFrameNumber
|
|
1420
|
+
} = (0,getSOPInstanceAttributes/* default */.Z)(imageId);
|
|
1421
|
+
return aSOPInstanceUID === SOPInstanceUID && (!frameNumber || frameNumber === aFrameNumber);
|
|
1422
|
+
});
|
|
1423
|
+
} else {
|
|
1424
|
+
// for volume viewport we can't rely on the imageIdIndex since it can be
|
|
1425
|
+
// a reconstructed view that doesn't match the original slice numbers etc.
|
|
1426
|
+
const {
|
|
1427
|
+
viewPlaneNormal: measurementViewPlane
|
|
1428
|
+
} = measurement.metadata;
|
|
1429
|
+
imageIdIndex = referencedDisplaySet.images.findIndex(i => i.SOPInstanceUID === SOPInstanceUID);
|
|
1430
|
+
const {
|
|
1431
|
+
viewPlaneNormal: viewportViewPlane
|
|
1432
|
+
} = viewport.getCamera();
|
|
1433
|
+
|
|
1434
|
+
// should compare abs for both planes since the direction can be flipped
|
|
1435
|
+
if (measurementViewPlane && !dist_esm.utilities.isEqual(measurementViewPlane.map(Math.abs), viewportViewPlane.map(Math.abs))) {
|
|
1436
|
+
viewportCameraDirectionMatch = false;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (!viewportCameraDirectionMatch || imageIdIndex === -1) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
esm.utilities.jumpToSlice(targetElement, {
|
|
1443
|
+
imageIndex: imageIdIndex
|
|
1444
|
+
});
|
|
1445
|
+
esm.annotation.selection.setAnnotationSelected(measurement.uid);
|
|
1446
|
+
// Jump to measurement consumed, remove.
|
|
1447
|
+
cacheJumpToMeasurementEvent?.consume?.();
|
|
1448
|
+
cacheJumpToMeasurementEvent = null;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Component displayName
|
|
1453
|
+
OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
|
|
1454
|
+
OHIFCornerstoneViewport.propTypes = {
|
|
1455
|
+
viewportIndex: (prop_types_default()).number.isRequired,
|
|
1456
|
+
displaySets: (prop_types_default()).array.isRequired,
|
|
1457
|
+
dataSource: (prop_types_default()).object.isRequired,
|
|
1458
|
+
viewportOptions: (prop_types_default()).object,
|
|
1459
|
+
displaySetOptions: prop_types_default().arrayOf((prop_types_default()).any),
|
|
1460
|
+
servicesManager: (prop_types_default()).object.isRequired,
|
|
1461
|
+
onElementEnabled: (prop_types_default()).func,
|
|
1462
|
+
// Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
|
|
1463
|
+
// of the imageData in the OHIFCornerstoneViewport. This prop is used
|
|
1464
|
+
// to set the initial state of the viewport's first image to render
|
|
1465
|
+
initialImageIdOrIndex: prop_types_default().oneOfType([(prop_types_default()).string, (prop_types_default()).number])
|
|
1466
|
+
};
|
|
1467
|
+
/* harmony default export */ const Viewport_OHIFCornerstoneViewport = (OHIFCornerstoneViewport);
|
|
1468
|
+
|
|
1469
|
+
/***/ })
|
|
1470
|
+
|
|
1471
|
+
}]);
|