@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,2725 @@
|
|
|
1
|
+
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[976],{
|
|
2
|
+
|
|
3
|
+
/***/ 77594:
|
|
4
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
// ESM COMPAT FLAG
|
|
8
|
+
__webpack_require__.r(__webpack_exports__);
|
|
9
|
+
|
|
10
|
+
// EXPORTS
|
|
11
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
12
|
+
"default": () => (/* binding */ dicom_microscopy_src)
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/package.json
|
|
16
|
+
const package_namespaceObject = JSON.parse('{"u2":"@ohif/extension-dicom-microscopy"}');
|
|
17
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/id.js
|
|
18
|
+
|
|
19
|
+
const id = package_namespaceObject.u2;
|
|
20
|
+
|
|
21
|
+
// EXTERNAL MODULE: ../../../node_modules/react/index.js
|
|
22
|
+
var react = __webpack_require__(32735);
|
|
23
|
+
// EXTERNAL MODULE: ../../ui/src/index.js + 449 modules
|
|
24
|
+
var src = __webpack_require__(43803);
|
|
25
|
+
// EXTERNAL MODULE: ../../core/src/index.ts + 101 modules
|
|
26
|
+
var core_src = __webpack_require__(48501);
|
|
27
|
+
// EXTERNAL MODULE: ../../../node_modules/react-i18next/dist/es/index.js + 15 modules
|
|
28
|
+
var es = __webpack_require__(21572);
|
|
29
|
+
// EXTERNAL MODULE: ../../../node_modules/mathjs/lib/esm/index.js + 972 modules
|
|
30
|
+
var esm = __webpack_require__(29926);
|
|
31
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// TODO -> This is pulled out of some internal logic from Dicom Microscopy Viewer,
|
|
35
|
+
// We should likely just expose this there.
|
|
36
|
+
|
|
37
|
+
function coordinateFormatScoord3d2Geometry(coordinates, pyramid) {
|
|
38
|
+
let transform = false;
|
|
39
|
+
if (!Array.isArray(coordinates[0])) {
|
|
40
|
+
coordinates = [coordinates];
|
|
41
|
+
transform = true;
|
|
42
|
+
}
|
|
43
|
+
const metadata = pyramid[pyramid.length - 1];
|
|
44
|
+
const orientation = metadata.ImageOrientationSlide;
|
|
45
|
+
const spacing = _getPixelSpacing(metadata);
|
|
46
|
+
const origin = metadata.TotalPixelMatrixOriginSequence[0];
|
|
47
|
+
const offset = [Number(origin.XOffsetInSlideCoordinateSystem), Number(origin.YOffsetInSlideCoordinateSystem)];
|
|
48
|
+
coordinates = coordinates.map(c => {
|
|
49
|
+
const slideCoord = [c[0], c[1]];
|
|
50
|
+
const pixelCoord = mapSlideCoord2PixelCoord({
|
|
51
|
+
offset,
|
|
52
|
+
orientation,
|
|
53
|
+
spacing,
|
|
54
|
+
point: slideCoord
|
|
55
|
+
});
|
|
56
|
+
return [pixelCoord[0], -(pixelCoord[1] + 1), 0];
|
|
57
|
+
});
|
|
58
|
+
if (transform) {
|
|
59
|
+
return coordinates[0];
|
|
60
|
+
}
|
|
61
|
+
return coordinates;
|
|
62
|
+
}
|
|
63
|
+
function _getPixelSpacing(metadata) {
|
|
64
|
+
if (metadata.PixelSpacing) return metadata.PixelSpacing;
|
|
65
|
+
const functionalGroup = metadata.SharedFunctionalGroupsSequence[0];
|
|
66
|
+
const pixelMeasures = functionalGroup.PixelMeasuresSequence[0];
|
|
67
|
+
return pixelMeasures.PixelSpacing;
|
|
68
|
+
}
|
|
69
|
+
function mapSlideCoord2PixelCoord(options) {
|
|
70
|
+
// X and Y Offset in Slide Coordinate System
|
|
71
|
+
if (!('offset' in options)) {
|
|
72
|
+
throw new Error('Option "offset" is required.');
|
|
73
|
+
}
|
|
74
|
+
if (!Array.isArray(options.offset)) {
|
|
75
|
+
throw new Error('Option "offset" must be an array.');
|
|
76
|
+
}
|
|
77
|
+
if (options.offset.length !== 2) {
|
|
78
|
+
throw new Error('Option "offset" must be an array with 2 elements.');
|
|
79
|
+
}
|
|
80
|
+
const offset = options.offset;
|
|
81
|
+
|
|
82
|
+
// Image Orientation Slide with direction cosines for Row and Column direction
|
|
83
|
+
if (!('orientation' in options)) {
|
|
84
|
+
throw new Error('Option "orientation" is required.');
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(options.orientation)) {
|
|
87
|
+
throw new Error('Option "orientation" must be an array.');
|
|
88
|
+
}
|
|
89
|
+
if (options.orientation.length !== 6) {
|
|
90
|
+
throw new Error('Option "orientation" must be an array with 6 elements.');
|
|
91
|
+
}
|
|
92
|
+
const orientation = options.orientation;
|
|
93
|
+
|
|
94
|
+
// Pixel Spacing along the Row and Column direction
|
|
95
|
+
if (!('spacing' in options)) {
|
|
96
|
+
throw new Error('Option "spacing" is required.');
|
|
97
|
+
}
|
|
98
|
+
if (!Array.isArray(options.spacing)) {
|
|
99
|
+
throw new Error('Option "spacing" must be an array.');
|
|
100
|
+
}
|
|
101
|
+
if (options.spacing.length !== 2) {
|
|
102
|
+
throw new Error('Option "spacing" must be an array with 2 elements.');
|
|
103
|
+
}
|
|
104
|
+
const spacing = options.spacing;
|
|
105
|
+
|
|
106
|
+
// X and Y coordinate in the Slide Coordinate System
|
|
107
|
+
if (!('point' in options)) {
|
|
108
|
+
throw new Error('Option "point" is required.');
|
|
109
|
+
}
|
|
110
|
+
if (!Array.isArray(options.point)) {
|
|
111
|
+
throw new Error('Option "point" must be an array.');
|
|
112
|
+
}
|
|
113
|
+
if (options.point.length !== 2) {
|
|
114
|
+
throw new Error('Option "point" must be an array with 2 elements.');
|
|
115
|
+
}
|
|
116
|
+
const point = options.point;
|
|
117
|
+
const m = [[orientation[0] * spacing[1], orientation[3] * spacing[0], offset[0]], [orientation[1] * spacing[1], orientation[4] * spacing[0], offset[1]], [0, 0, 1]];
|
|
118
|
+
const mInverted = (0,esm/* inv */.JBn)(m);
|
|
119
|
+
const vSlide = [[point[0]], [point[1]], [1]];
|
|
120
|
+
const vImage = (0,esm/* multiply */.JpY)(mInverted, vSlide);
|
|
121
|
+
const row = Number(vImage[1][0].toFixed(4));
|
|
122
|
+
const col = Number(vImage[0][0].toFixed(4));
|
|
123
|
+
return [col, row];
|
|
124
|
+
}
|
|
125
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/styles.js
|
|
126
|
+
const defaultFill = {
|
|
127
|
+
color: 'rgba(255,255,255,0.4)'
|
|
128
|
+
};
|
|
129
|
+
const emptyFill = {
|
|
130
|
+
color: 'rgba(255,255,255,0.0)'
|
|
131
|
+
};
|
|
132
|
+
const defaultStroke = {
|
|
133
|
+
color: 'rgb(0,255,0)',
|
|
134
|
+
width: 1.5
|
|
135
|
+
};
|
|
136
|
+
const activeStroke = {
|
|
137
|
+
color: 'rgb(255,255,0)',
|
|
138
|
+
width: 1.5
|
|
139
|
+
};
|
|
140
|
+
const defaultStyle = {
|
|
141
|
+
image: {
|
|
142
|
+
circle: {
|
|
143
|
+
fill: defaultFill,
|
|
144
|
+
stroke: activeStroke,
|
|
145
|
+
radius: 5
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
fill: defaultFill,
|
|
149
|
+
stroke: activeStroke
|
|
150
|
+
};
|
|
151
|
+
const emptyStyle = {
|
|
152
|
+
image: {
|
|
153
|
+
circle: {
|
|
154
|
+
fill: emptyFill,
|
|
155
|
+
stroke: defaultStroke,
|
|
156
|
+
radius: 5
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
fill: emptyFill,
|
|
160
|
+
stroke: defaultStroke
|
|
161
|
+
};
|
|
162
|
+
const styles = {
|
|
163
|
+
active: defaultStyle,
|
|
164
|
+
default: emptyStyle
|
|
165
|
+
};
|
|
166
|
+
/* harmony default export */ const utils_styles = (styles);
|
|
167
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/tools/viewerManager.js
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// Events from the third-party viewer
|
|
173
|
+
const ApiEvents = {
|
|
174
|
+
/** Triggered when a ROI was added. */
|
|
175
|
+
ROI_ADDED: 'dicommicroscopyviewer_roi_added',
|
|
176
|
+
/** Triggered when a ROI was modified. */
|
|
177
|
+
ROI_MODIFIED: 'dicommicroscopyviewer_roi_modified',
|
|
178
|
+
/** Triggered when a ROI was removed. */
|
|
179
|
+
ROI_REMOVED: 'dicommicroscopyviewer_roi_removed',
|
|
180
|
+
/** Triggered when a ROI was drawn. */
|
|
181
|
+
ROI_DRAWN: `dicommicroscopyviewer_roi_drawn`,
|
|
182
|
+
/** Triggered when a ROI was selected. */
|
|
183
|
+
ROI_SELECTED: `dicommicroscopyviewer_roi_selected`,
|
|
184
|
+
/** Triggered when a viewport move has started. */
|
|
185
|
+
MOVE_STARTED: `dicommicroscopyviewer_move_started`,
|
|
186
|
+
/** Triggered when a viewport move has ended. */
|
|
187
|
+
MOVE_ENDED: `dicommicroscopyviewer_move_ended`,
|
|
188
|
+
/** Triggered when a loading of data has started. */
|
|
189
|
+
LOADING_STARTED: `dicommicroscopyviewer_loading_started`,
|
|
190
|
+
/** Triggered when a loading of data has ended. */
|
|
191
|
+
LOADING_ENDED: `dicommicroscopyviewer_loading_ended`,
|
|
192
|
+
/** Triggered when an error occurs during loading of data. */
|
|
193
|
+
LOADING_ERROR: `dicommicroscopyviewer_loading_error`,
|
|
194
|
+
/* Triggered when the loading of an image tile has started. */
|
|
195
|
+
FRAME_LOADING_STARTED: `dicommicroscopyviewer_frame_loading_started`,
|
|
196
|
+
/* Triggered when the loading of an image tile has ended. */
|
|
197
|
+
FRAME_LOADING_ENDED: `dicommicroscopyviewer_frame_loading_ended`,
|
|
198
|
+
/* Triggered when the error occurs during loading of an image tile. */
|
|
199
|
+
FRAME_LOADING_ERROR: `dicommicroscopyviewer_frame_loading_ended`
|
|
200
|
+
};
|
|
201
|
+
const EVENTS = {
|
|
202
|
+
ADDED: 'added',
|
|
203
|
+
MODIFIED: 'modified',
|
|
204
|
+
REMOVED: 'removed',
|
|
205
|
+
UPDATED: 'updated',
|
|
206
|
+
SELECTED: 'selected'
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* ViewerManager encapsulates the complexity of the third-party viewer and
|
|
211
|
+
* expose only the features/behaviors that are relevant to the application
|
|
212
|
+
*/
|
|
213
|
+
class ViewerManager extends core_src/* PubSubService */.hC {
|
|
214
|
+
constructor(viewer, viewportIndex, container, studyInstanceUID, seriesInstanceUID) {
|
|
215
|
+
super(EVENTS);
|
|
216
|
+
this.viewer = viewer;
|
|
217
|
+
this.viewportIndex = viewportIndex;
|
|
218
|
+
this.container = container;
|
|
219
|
+
this.studyInstanceUID = studyInstanceUID;
|
|
220
|
+
this.seriesInstanceUID = seriesInstanceUID;
|
|
221
|
+
this.onRoiAdded = this.roiAddedHandler.bind(this);
|
|
222
|
+
this.onRoiModified = this.roiModifiedHandler.bind(this);
|
|
223
|
+
this.onRoiRemoved = this.roiRemovedHandler.bind(this);
|
|
224
|
+
this.onRoiSelected = this.roiSelectedHandler.bind(this);
|
|
225
|
+
this.contextMenuCallback = () => {};
|
|
226
|
+
|
|
227
|
+
// init symbols
|
|
228
|
+
const symbols = Object.getOwnPropertySymbols(this.viewer);
|
|
229
|
+
this._drawingSource = symbols.find(p => p.description === 'drawingSource');
|
|
230
|
+
this._pyramid = symbols.find(p => p.description === 'pyramid');
|
|
231
|
+
this._map = symbols.find(p => p.description === 'map');
|
|
232
|
+
this._affine = symbols.find(p => p.description === 'affine');
|
|
233
|
+
this.registerEvents();
|
|
234
|
+
this.activateDefaultInteractions();
|
|
235
|
+
}
|
|
236
|
+
addContextMenuCallback(callback) {
|
|
237
|
+
this.contextMenuCallback = callback;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Destroys this managed viewer instance, clearing all the event handlers
|
|
242
|
+
*/
|
|
243
|
+
destroy() {
|
|
244
|
+
this.unregisterEvents();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* This is to overrides the _broadcastEvent method of PubSubService and always
|
|
249
|
+
* send the ROI graphic object and this managed viewer instance.
|
|
250
|
+
* Due to the way that PubSubService is written, the same name override of the
|
|
251
|
+
* function doesn't work.
|
|
252
|
+
*
|
|
253
|
+
* @param {String} key key Subscription key
|
|
254
|
+
* @param {Object} roiGraphic ROI graphic object created by the third-party API
|
|
255
|
+
*/
|
|
256
|
+
publish(key, roiGraphic) {
|
|
257
|
+
this._broadcastEvent(key, {
|
|
258
|
+
roiGraphic,
|
|
259
|
+
managedViewer: this
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Registers all the relevant event handlers for the third-party API
|
|
265
|
+
*/
|
|
266
|
+
registerEvents() {
|
|
267
|
+
this.container.addEventListener(ApiEvents.ROI_ADDED, this.onRoiAdded);
|
|
268
|
+
this.container.addEventListener(ApiEvents.ROI_MODIFIED, this.onRoiModified);
|
|
269
|
+
this.container.addEventListener(ApiEvents.ROI_REMOVED, this.onRoiRemoved);
|
|
270
|
+
this.container.addEventListener(ApiEvents.ROI_SELECTED, this.onRoiSelected);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Cleares all the relevant event handlers for the third-party API
|
|
275
|
+
*/
|
|
276
|
+
unregisterEvents() {
|
|
277
|
+
this.container.removeEventListener(ApiEvents.ROI_ADDED, this.onRoiAdded);
|
|
278
|
+
this.container.removeEventListener(ApiEvents.ROI_MODIFIED, this.onRoiModified);
|
|
279
|
+
this.container.removeEventListener(ApiEvents.ROI_REMOVED, this.onRoiRemoved);
|
|
280
|
+
this.container.removeEventListener(ApiEvents.ROI_SELECTED, this.onRoiSelected);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Handles the ROI_ADDED event triggered by the third-party API
|
|
285
|
+
*
|
|
286
|
+
* @param {Event} event Event triggered by the third-party API
|
|
287
|
+
*/
|
|
288
|
+
roiAddedHandler(event) {
|
|
289
|
+
const roiGraphic = event.detail.payload;
|
|
290
|
+
this.publish(EVENTS.ADDED, roiGraphic);
|
|
291
|
+
this.publish(EVENTS.UPDATED, roiGraphic);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Handles the ROI_MODIFIED event triggered by the third-party API
|
|
296
|
+
*
|
|
297
|
+
* @param {Event} event Event triggered by the third-party API
|
|
298
|
+
*/
|
|
299
|
+
roiModifiedHandler(event) {
|
|
300
|
+
const roiGraphic = event.detail.payload;
|
|
301
|
+
this.publish(EVENTS.MODIFIED, roiGraphic);
|
|
302
|
+
this.publish(EVENTS.UPDATED, roiGraphic);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Handles the ROI_REMOVED event triggered by the third-party API
|
|
307
|
+
*
|
|
308
|
+
* @param {Event} event Event triggered by the third-party API
|
|
309
|
+
*/
|
|
310
|
+
roiRemovedHandler(event) {
|
|
311
|
+
const roiGraphic = event.detail.payload;
|
|
312
|
+
this.publish(EVENTS.REMOVED, roiGraphic);
|
|
313
|
+
this.publish(EVENTS.UPDATED, roiGraphic);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Handles the ROI_SELECTED event triggered by the third-party API
|
|
318
|
+
*
|
|
319
|
+
* @param {Event} event Event triggered by the third-party API
|
|
320
|
+
*/
|
|
321
|
+
roiSelectedHandler(event) {
|
|
322
|
+
const roiGraphic = event.detail.payload;
|
|
323
|
+
this.publish(EVENTS.SELECTED, roiGraphic);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Run the given callback operation without triggering any events for this
|
|
328
|
+
* instance, so subscribers will not be affected
|
|
329
|
+
*
|
|
330
|
+
* @param {Function} callback Callback that will run sinlently
|
|
331
|
+
*/
|
|
332
|
+
runSilently(callback) {
|
|
333
|
+
this.unregisterEvents();
|
|
334
|
+
callback();
|
|
335
|
+
this.registerEvents();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Removes all the ROI graphics from the third-party API
|
|
340
|
+
*/
|
|
341
|
+
clearRoiGraphics() {
|
|
342
|
+
this.runSilently(() => this.viewer.removeAllROIs());
|
|
343
|
+
}
|
|
344
|
+
showROIs() {
|
|
345
|
+
this.viewer.showROIs();
|
|
346
|
+
}
|
|
347
|
+
hideROIs() {
|
|
348
|
+
this.viewer.hideROIs();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Adds the given ROI graphic into the third-party API
|
|
353
|
+
*
|
|
354
|
+
* @param {Object} roiGraphic ROI graphic object to be added
|
|
355
|
+
*/
|
|
356
|
+
addRoiGraphic(roiGraphic) {
|
|
357
|
+
this.runSilently(() => this.viewer.addROI(roiGraphic, utils_styles["default"]));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Adds the given ROI graphic into the third-party API, and also add a label.
|
|
362
|
+
* Used for importing from SR.
|
|
363
|
+
*
|
|
364
|
+
* @param {Object} roiGraphic ROI graphic object to be added.
|
|
365
|
+
* @param {String} label The label of the annotation.
|
|
366
|
+
*/
|
|
367
|
+
addRoiGraphicWithLabel(roiGraphic, label) {
|
|
368
|
+
// NOTE: Dicom Microscopy Viewer will override styles for "Text" evalutations
|
|
369
|
+
// to hide all other geometries, we are not going to use its label.
|
|
370
|
+
// if (label) {
|
|
371
|
+
// if (!roiGraphic.properties) roiGraphic.properties = {};
|
|
372
|
+
// roiGraphic.properties.label = label;
|
|
373
|
+
// }
|
|
374
|
+
this.runSilently(() => this.viewer.addROI(roiGraphic, utils_styles["default"]));
|
|
375
|
+
this._broadcastEvent(EVENTS.ADDED, {
|
|
376
|
+
roiGraphic,
|
|
377
|
+
managedViewer: this,
|
|
378
|
+
label
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Sets ROI style
|
|
384
|
+
*
|
|
385
|
+
* @param {String} uid ROI graphic UID to be styled
|
|
386
|
+
* @param {object} styleOptions - Style options
|
|
387
|
+
* @param {object} styleOptions.stroke - Style options for the outline of the geometry
|
|
388
|
+
* @param {number[]} styleOptions.stroke.color - RGBA color of the outline
|
|
389
|
+
* @param {number} styleOptions.stroke.width - Width of the outline
|
|
390
|
+
* @param {object} styleOptions.fill - Style options for body the geometry
|
|
391
|
+
* @param {number[]} styleOptions.fill.color - RGBA color of the body
|
|
392
|
+
* @param {object} styleOptions.image - Style options for image
|
|
393
|
+
*/
|
|
394
|
+
setROIStyle(uid, styleOptions) {
|
|
395
|
+
this.viewer.setROIStyle(uid, styleOptions);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Removes the ROI graphic with the given UID from the third-party API
|
|
400
|
+
*
|
|
401
|
+
* @param {String} uid ROI graphic UID to be removed
|
|
402
|
+
*/
|
|
403
|
+
removeRoiGraphic(uid) {
|
|
404
|
+
this.viewer.removeROI(uid);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Update properties of regions of interest.
|
|
409
|
+
*
|
|
410
|
+
* @param {object} roi - ROI to be updated
|
|
411
|
+
* @param {string} roi.uid - Unique identifier of the region of interest
|
|
412
|
+
* @param {object} roi.properties - ROI properties
|
|
413
|
+
* @returns {void}
|
|
414
|
+
*/
|
|
415
|
+
updateROIProperties(_ref) {
|
|
416
|
+
let {
|
|
417
|
+
uid,
|
|
418
|
+
properties
|
|
419
|
+
} = _ref;
|
|
420
|
+
this.viewer.updateROI({
|
|
421
|
+
uid,
|
|
422
|
+
properties
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Toggles overview map
|
|
428
|
+
*
|
|
429
|
+
* @returns {void}
|
|
430
|
+
*/
|
|
431
|
+
toggleOverviewMap() {
|
|
432
|
+
this.viewer.toggleOverviewMap();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Activates the viewer default interactions
|
|
437
|
+
* @returns {void}
|
|
438
|
+
*/
|
|
439
|
+
activateDefaultInteractions() {
|
|
440
|
+
/** Disable browser's native context menu inside the canvas */
|
|
441
|
+
document.querySelector('.DicomMicroscopyViewer').addEventListener('contextmenu', event => {
|
|
442
|
+
event.preventDefault();
|
|
443
|
+
// comment out when context menu for microscopy is enabled
|
|
444
|
+
// if (typeof this.contextMenuCallback === 'function') {
|
|
445
|
+
// this.contextMenuCallback(event);
|
|
446
|
+
// }
|
|
447
|
+
}, false);
|
|
448
|
+
const defaultInteractions = [['dragPan', {
|
|
449
|
+
bindings: {
|
|
450
|
+
mouseButtons: ['middle']
|
|
451
|
+
}
|
|
452
|
+
}], ['dragZoom', {
|
|
453
|
+
bindings: {
|
|
454
|
+
mouseButtons: ['right']
|
|
455
|
+
}
|
|
456
|
+
}], ['modify', {}]];
|
|
457
|
+
this.activateInteractions(defaultInteractions);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Activates interactions
|
|
462
|
+
* @param {Array} interactions Interactions to be activated
|
|
463
|
+
* @returns {void}
|
|
464
|
+
*/
|
|
465
|
+
activateInteractions(interactions) {
|
|
466
|
+
const interactionsMap = {
|
|
467
|
+
draw: activate => activate ? 'activateDrawInteraction' : 'deactivateDrawInteraction',
|
|
468
|
+
modify: activate => activate ? 'activateModifyInteraction' : 'deactivateModifyInteraction',
|
|
469
|
+
translate: activate => activate ? 'activateTranslateInteraction' : 'deactivateTranslateInteraction',
|
|
470
|
+
snap: activate => activate ? 'activateSnapInteraction' : 'deactivateSnapInteraction',
|
|
471
|
+
dragPan: activate => activate ? 'activateDragPanInteraction' : 'deactivateDragPanInteraction',
|
|
472
|
+
dragZoom: activate => activate ? 'activateDragZoomInteraction' : 'deactivateDragZoomInteraction',
|
|
473
|
+
select: activate => activate ? 'activateSelectInteraction' : 'deactivateSelectInteraction'
|
|
474
|
+
};
|
|
475
|
+
const availableInteractionsName = Object.keys(interactionsMap);
|
|
476
|
+
availableInteractionsName.forEach(availableInteractionName => {
|
|
477
|
+
const interaction = interactions.find(interaction => interaction[0] === availableInteractionName);
|
|
478
|
+
if (!interaction) {
|
|
479
|
+
const deactivateInteractionMethod = interactionsMap[availableInteractionName](false);
|
|
480
|
+
this.viewer[deactivateInteractionMethod]();
|
|
481
|
+
} else {
|
|
482
|
+
const [name, config] = interaction;
|
|
483
|
+
const activateInteractionMethod = interactionsMap[name](true);
|
|
484
|
+
this.viewer[activateInteractionMethod](config);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Accesses the internals of third-party API and returns the OpenLayers Map
|
|
491
|
+
*
|
|
492
|
+
* @returns {Object} OpenLayers Map component instance
|
|
493
|
+
*/
|
|
494
|
+
_getMapView() {
|
|
495
|
+
const map = this._getMap();
|
|
496
|
+
return map.getView();
|
|
497
|
+
}
|
|
498
|
+
_getMap() {
|
|
499
|
+
const symbols = Object.getOwnPropertySymbols(this.viewer);
|
|
500
|
+
const _map = symbols.find(s => String(s) === 'Symbol(map)');
|
|
501
|
+
window['map'] = this.viewer[_map];
|
|
502
|
+
return this.viewer[_map];
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Returns the current state for the OpenLayers View
|
|
507
|
+
*
|
|
508
|
+
* @returns {Object} Current view state
|
|
509
|
+
*/
|
|
510
|
+
getViewState() {
|
|
511
|
+
const view = this._getMapView();
|
|
512
|
+
return {
|
|
513
|
+
center: view.getCenter(),
|
|
514
|
+
resolution: view.getResolution(),
|
|
515
|
+
zoom: view.getZoom()
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Sets the current state for the OpenLayers View
|
|
521
|
+
*
|
|
522
|
+
* @param {Object} viewState View state to be applied
|
|
523
|
+
*/
|
|
524
|
+
setViewState(viewState) {
|
|
525
|
+
const view = this._getMapView();
|
|
526
|
+
view.setZoom(viewState.zoom);
|
|
527
|
+
view.setResolution(viewState.resolution);
|
|
528
|
+
view.setCenter(viewState.center);
|
|
529
|
+
}
|
|
530
|
+
setViewStateByExtent(roiAnnotation) {
|
|
531
|
+
const coordinates = roiAnnotation.getCoordinates();
|
|
532
|
+
if (Array.isArray(coordinates[0]) && !coordinates[2]) {
|
|
533
|
+
this._jumpToPolyline(coordinates);
|
|
534
|
+
} else if (Array.isArray(coordinates[0])) {
|
|
535
|
+
this._jumpToPolygonOrEllipse(coordinates);
|
|
536
|
+
} else {
|
|
537
|
+
this._jumpToPoint(coordinates);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
_jumpToPoint(coord) {
|
|
541
|
+
const pyramid = this.viewer[this._pyramid].metadata;
|
|
542
|
+
const mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
|
|
543
|
+
const view = this._getMapView();
|
|
544
|
+
view.setCenter(mappedCoord);
|
|
545
|
+
}
|
|
546
|
+
_jumpToPolyline(coord) {
|
|
547
|
+
const pyramid = this.viewer[this._pyramid].metadata;
|
|
548
|
+
const mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
|
|
549
|
+
const view = this._getMapView();
|
|
550
|
+
const x = mappedCoord[0];
|
|
551
|
+
const y = mappedCoord[1];
|
|
552
|
+
const xab = (x[0] + y[0]) / 2;
|
|
553
|
+
const yab = (x[1] + y[1]) / 2;
|
|
554
|
+
const midpoint = [xab, yab];
|
|
555
|
+
view.setCenter(midpoint);
|
|
556
|
+
}
|
|
557
|
+
_jumpToPolygonOrEllipse(coordinates) {
|
|
558
|
+
const pyramid = this.viewer[this._pyramid].metadata;
|
|
559
|
+
let minX = Infinity;
|
|
560
|
+
let maxX = -Infinity;
|
|
561
|
+
let minY = Infinity;
|
|
562
|
+
let maxY = -Infinity;
|
|
563
|
+
coordinates.forEach(coord => {
|
|
564
|
+
let mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
|
|
565
|
+
const [x, y] = mappedCoord;
|
|
566
|
+
if (x < minX) {
|
|
567
|
+
minX = x;
|
|
568
|
+
} else if (x > maxX) {
|
|
569
|
+
maxX = x;
|
|
570
|
+
}
|
|
571
|
+
if (y < minY) {
|
|
572
|
+
minY = y;
|
|
573
|
+
} else if (y > maxY) {
|
|
574
|
+
maxY = y;
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
const width = maxX - minX;
|
|
578
|
+
const height = maxY - minY;
|
|
579
|
+
minX -= 0.5 * width;
|
|
580
|
+
maxX += 0.5 * width;
|
|
581
|
+
minY -= 0.5 * height;
|
|
582
|
+
maxY += 0.5 * height;
|
|
583
|
+
const map = this._getMap();
|
|
584
|
+
map.getView().fit([minX, minY, maxX, maxY], map.getSize());
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* harmony default export */ const viewerManager = (ViewerManager);
|
|
589
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/areaOfPolygon.js
|
|
590
|
+
function areaOfPolygon(coordinates) {
|
|
591
|
+
// Shoelace algorithm.
|
|
592
|
+
const n = coordinates.length;
|
|
593
|
+
let area = 0.0;
|
|
594
|
+
let j = n - 1;
|
|
595
|
+
for (let i = 0; i < n; i++) {
|
|
596
|
+
area += (coordinates[j][0] + coordinates[i][0]) * (coordinates[j][1] - coordinates[i][1]);
|
|
597
|
+
j = i; // j is previous vertex to i
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Return absolute value of half the sum
|
|
601
|
+
// (The value is halved as we are summing up triangles, not rectangles).
|
|
602
|
+
return Math.abs(area / 2.0);
|
|
603
|
+
}
|
|
604
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/RoiAnnotation.js
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
const RoiAnnotation_EVENTS = {
|
|
608
|
+
LABEL_UPDATED: 'labelUpdated',
|
|
609
|
+
GRAPHIC_UPDATED: 'graphicUpdated',
|
|
610
|
+
VIEW_UPDATED: 'viewUpdated',
|
|
611
|
+
REMOVED: 'removed'
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Represents a single annotation for the Microscopy Viewer
|
|
616
|
+
*/
|
|
617
|
+
class RoiAnnotation extends core_src/* PubSubService */.hC {
|
|
618
|
+
constructor(roiGraphic, studyInstanceUID, seriesInstanceUID) {
|
|
619
|
+
let label = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
|
|
620
|
+
let viewState = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
|
|
621
|
+
super(RoiAnnotation_EVENTS);
|
|
622
|
+
this.uid = roiGraphic.uid;
|
|
623
|
+
this.roiGraphic = roiGraphic;
|
|
624
|
+
this.studyInstanceUID = studyInstanceUID;
|
|
625
|
+
this.seriesInstanceUID = seriesInstanceUID;
|
|
626
|
+
this.label = label;
|
|
627
|
+
this.viewState = viewState;
|
|
628
|
+
this.setMeasurements(roiGraphic);
|
|
629
|
+
}
|
|
630
|
+
getScoord3d() {
|
|
631
|
+
const roiGraphic = this.roiGraphic;
|
|
632
|
+
const roiGraphicSymbols = Object.getOwnPropertySymbols(roiGraphic);
|
|
633
|
+
const _scoord3d = roiGraphicSymbols.find(s => String(s) === 'Symbol(scoord3d)');
|
|
634
|
+
return roiGraphic[_scoord3d];
|
|
635
|
+
}
|
|
636
|
+
getCoordinates() {
|
|
637
|
+
const scoord3d = this.getScoord3d();
|
|
638
|
+
const scoord3dSymbols = Object.getOwnPropertySymbols(scoord3d);
|
|
639
|
+
const _coordinates = scoord3dSymbols.find(s => String(s) === 'Symbol(coordinates)');
|
|
640
|
+
const coordinates = scoord3d[_coordinates];
|
|
641
|
+
return coordinates;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* When called will trigger the REMOVED event
|
|
646
|
+
*/
|
|
647
|
+
destroy() {
|
|
648
|
+
this._broadcastEvent(RoiAnnotation_EVENTS.REMOVED, this);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Updates the ROI graphic for the annotation and triggers the GRAPHIC_UPDATED
|
|
653
|
+
* event
|
|
654
|
+
*
|
|
655
|
+
* @param {Object} roiGraphic
|
|
656
|
+
*/
|
|
657
|
+
setRoiGraphic(roiGraphic) {
|
|
658
|
+
this.roiGraphic = roiGraphic;
|
|
659
|
+
this.setMeasurements();
|
|
660
|
+
this._broadcastEvent(RoiAnnotation_EVENTS.GRAPHIC_UPDATED, this);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Update ROI measurement values based on its scoord3d coordinates.
|
|
665
|
+
*
|
|
666
|
+
* @returns {void}
|
|
667
|
+
*/
|
|
668
|
+
setMeasurements() {
|
|
669
|
+
const type = this.roiGraphic.scoord3d.graphicType;
|
|
670
|
+
const coordinates = this.roiGraphic.scoord3d.graphicData;
|
|
671
|
+
switch (type) {
|
|
672
|
+
case 'ELLIPSE':
|
|
673
|
+
// This is a circle so only need one side
|
|
674
|
+
const point1 = coordinates[0];
|
|
675
|
+
const point2 = coordinates[1];
|
|
676
|
+
let xLength2 = point2[0] - point1[0];
|
|
677
|
+
let yLength2 = point2[1] - point1[1];
|
|
678
|
+
xLength2 *= xLength2;
|
|
679
|
+
yLength2 *= yLength2;
|
|
680
|
+
const length = Math.sqrt(xLength2 + yLength2);
|
|
681
|
+
const radius = length / 2;
|
|
682
|
+
const areaEllipse = Math.PI * radius * radius;
|
|
683
|
+
this._area = areaEllipse;
|
|
684
|
+
this._length = undefined;
|
|
685
|
+
break;
|
|
686
|
+
case 'POLYGON':
|
|
687
|
+
const areaPolygon = areaOfPolygon(coordinates);
|
|
688
|
+
this._area = areaPolygon;
|
|
689
|
+
this._length = undefined;
|
|
690
|
+
break;
|
|
691
|
+
case 'POINT':
|
|
692
|
+
this._area = undefined;
|
|
693
|
+
this._length = undefined;
|
|
694
|
+
break;
|
|
695
|
+
case 'POLYLINE':
|
|
696
|
+
let len = 0;
|
|
697
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
698
|
+
const p1 = coordinates[i - 1];
|
|
699
|
+
const p2 = coordinates[i];
|
|
700
|
+
let xLen = p2[0] - p1[0];
|
|
701
|
+
let yLen = p2[1] - p1[1];
|
|
702
|
+
xLen *= xLen;
|
|
703
|
+
yLen *= yLen;
|
|
704
|
+
len += Math.sqrt(xLen + yLen);
|
|
705
|
+
}
|
|
706
|
+
this._area = undefined;
|
|
707
|
+
this._length = len;
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Update the OpenLayer Map's view state for the annotation and triggers the
|
|
714
|
+
* VIEW_UPDATED event
|
|
715
|
+
*
|
|
716
|
+
* @param {Object} viewState The new view state for the annotation
|
|
717
|
+
*/
|
|
718
|
+
setViewState(viewState) {
|
|
719
|
+
this.viewState = viewState;
|
|
720
|
+
this._broadcastEvent(RoiAnnotation_EVENTS.VIEW_UPDATED, this);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Update the label for the annotation and triggers the LABEL_UPDATED event
|
|
725
|
+
*
|
|
726
|
+
* @param {String} label New label for the annotation
|
|
727
|
+
*/
|
|
728
|
+
setLabel(label, finding) {
|
|
729
|
+
this.label = label || finding && finding.CodeMeaning;
|
|
730
|
+
this.finding = finding || {
|
|
731
|
+
CodingSchemeDesignator: '@ohif/extension-dicom-microscopy',
|
|
732
|
+
CodeValue: label,
|
|
733
|
+
CodeMeaning: label
|
|
734
|
+
};
|
|
735
|
+
this._broadcastEvent(RoiAnnotation_EVENTS.LABEL_UPDATED, this);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Returns the geometry type of the annotation concatenated with the label
|
|
740
|
+
* defined for the annotation.
|
|
741
|
+
* Difference with getDetailedLabel() is that this will return empty string for empy
|
|
742
|
+
* label.
|
|
743
|
+
*
|
|
744
|
+
* @returns {String} Text with geometry type and label
|
|
745
|
+
*/
|
|
746
|
+
getLabel() {
|
|
747
|
+
const label = this.label ? `${this.label}` : '';
|
|
748
|
+
return label;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Returns the geometry type of the annotation concatenated with the label
|
|
753
|
+
* defined for the annotation
|
|
754
|
+
*
|
|
755
|
+
* @returns {String} Text with geometry type and label
|
|
756
|
+
*/
|
|
757
|
+
getDetailedLabel() {
|
|
758
|
+
const label = this.label ? `${this.label}` : '(empty)';
|
|
759
|
+
return label;
|
|
760
|
+
}
|
|
761
|
+
getLength() {
|
|
762
|
+
return this._length;
|
|
763
|
+
}
|
|
764
|
+
getArea() {
|
|
765
|
+
return this._area;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/* harmony default export */ const utils_RoiAnnotation = (RoiAnnotation);
|
|
770
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/services/MicroscopyService.ts
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
const MicroscopyService_EVENTS = {
|
|
776
|
+
ANNOTATION_UPDATED: 'annotationUpdated',
|
|
777
|
+
ANNOTATION_SELECTED: 'annotationSelected',
|
|
778
|
+
ANNOTATION_REMOVED: 'annotationRemoved',
|
|
779
|
+
RELABEL: 'relabel',
|
|
780
|
+
DELETE: 'delete'
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* MicroscopyService is responsible to manage multiple third-party API's
|
|
785
|
+
* microscopy viewers expose methods to manage the interaction with these
|
|
786
|
+
* viewers and handle their ROI graphics to create, remove and modify the
|
|
787
|
+
* ROI annotations relevant to the application
|
|
788
|
+
*/
|
|
789
|
+
class MicroscopyService extends core_src/* PubSubService */.hC {
|
|
790
|
+
constructor(serviceManager) {
|
|
791
|
+
super(MicroscopyService_EVENTS);
|
|
792
|
+
this.serviceManager = void 0;
|
|
793
|
+
this.managedViewers = new Set();
|
|
794
|
+
this.roiUids = new Set();
|
|
795
|
+
this.annotations = {};
|
|
796
|
+
this.selectedAnnotation = null;
|
|
797
|
+
this.pendingFocus = false;
|
|
798
|
+
this.serviceManager = serviceManager;
|
|
799
|
+
this._onRoiAdded = this._onRoiAdded.bind(this);
|
|
800
|
+
this._onRoiModified = this._onRoiModified.bind(this);
|
|
801
|
+
this._onRoiRemoved = this._onRoiRemoved.bind(this);
|
|
802
|
+
this._onRoiUpdated = this._onRoiUpdated.bind(this);
|
|
803
|
+
this._onRoiSelected = this._onRoiSelected.bind(this);
|
|
804
|
+
this.isROIsVisible = true;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Cleares all the annotations and managed viewers, setting the manager state
|
|
809
|
+
* to its initial state
|
|
810
|
+
*/
|
|
811
|
+
clear() {
|
|
812
|
+
this.managedViewers.forEach(managedViewer => managedViewer.destroy());
|
|
813
|
+
this.managedViewers.clear();
|
|
814
|
+
for (var key in this.annotations) {
|
|
815
|
+
delete this.annotations[key];
|
|
816
|
+
}
|
|
817
|
+
this.roiUids.clear();
|
|
818
|
+
this.selectedAnnotation = null;
|
|
819
|
+
this.pendingFocus = false;
|
|
820
|
+
}
|
|
821
|
+
clearAnnotations() {
|
|
822
|
+
Object.keys(this.annotations).forEach(uid => {
|
|
823
|
+
this.removeAnnotation(this.annotations[uid]);
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Observes when a ROI graphic is added, creating the correspondent annotation
|
|
829
|
+
* with the current graphic and view state.
|
|
830
|
+
* Creates a subscription for label updating for the created annotation and
|
|
831
|
+
* publishes an ANNOTATION_UPDATED event when it happens.
|
|
832
|
+
* Also triggers the relabel process after the graphic is placed.
|
|
833
|
+
*
|
|
834
|
+
* @param {Object} data The published data
|
|
835
|
+
* @param {Object} data.roiGraphic The added ROI graphic object
|
|
836
|
+
* @param {ViewerManager} data.managedViewer The origin viewer for the event
|
|
837
|
+
*/
|
|
838
|
+
_onRoiAdded(data) {
|
|
839
|
+
const {
|
|
840
|
+
roiGraphic,
|
|
841
|
+
managedViewer,
|
|
842
|
+
label
|
|
843
|
+
} = data;
|
|
844
|
+
const {
|
|
845
|
+
studyInstanceUID,
|
|
846
|
+
seriesInstanceUID
|
|
847
|
+
} = managedViewer;
|
|
848
|
+
const viewState = managedViewer.getViewState();
|
|
849
|
+
const roiAnnotation = new utils_RoiAnnotation(roiGraphic, studyInstanceUID, seriesInstanceUID, '', viewState);
|
|
850
|
+
this.roiUids.add(roiGraphic.uid);
|
|
851
|
+
this.annotations[roiGraphic.uid] = roiAnnotation;
|
|
852
|
+
roiAnnotation.subscribe(RoiAnnotation_EVENTS.LABEL_UPDATED, () => {
|
|
853
|
+
this._broadcastEvent(MicroscopyService_EVENTS.ANNOTATION_UPDATED, roiAnnotation);
|
|
854
|
+
});
|
|
855
|
+
if (label !== undefined) {
|
|
856
|
+
roiAnnotation.setLabel(label);
|
|
857
|
+
} else {
|
|
858
|
+
const onRelabel = item => managedViewer.updateROIProperties({
|
|
859
|
+
uid: roiGraphic.uid,
|
|
860
|
+
properties: {
|
|
861
|
+
label: item.label,
|
|
862
|
+
finding: item.finding
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
this.triggerRelabel(roiAnnotation, true, onRelabel);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Observes when a ROI graphic is modified, updating the correspondent
|
|
871
|
+
* annotation with the current graphic and view state.
|
|
872
|
+
*
|
|
873
|
+
* @param {Object} data The published data
|
|
874
|
+
* @param {Object} data.roiGraphic The modified ROI graphic object
|
|
875
|
+
*/
|
|
876
|
+
_onRoiModified(data) {
|
|
877
|
+
const {
|
|
878
|
+
roiGraphic,
|
|
879
|
+
managedViewer
|
|
880
|
+
} = data;
|
|
881
|
+
const roiAnnotation = this.getAnnotation(roiGraphic.uid);
|
|
882
|
+
if (!roiAnnotation) return;
|
|
883
|
+
roiAnnotation.setRoiGraphic(roiGraphic);
|
|
884
|
+
roiAnnotation.setViewState(managedViewer.getViewState());
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Observes when a ROI graphic is removed, reflecting the removal in the
|
|
889
|
+
* annotations' state.
|
|
890
|
+
*
|
|
891
|
+
* @param {Object} data The published data
|
|
892
|
+
* @param {Object} data.roiGraphic The removed ROI graphic object
|
|
893
|
+
*/
|
|
894
|
+
_onRoiRemoved(data) {
|
|
895
|
+
const {
|
|
896
|
+
roiGraphic
|
|
897
|
+
} = data;
|
|
898
|
+
this.roiUids.delete(roiGraphic.uid);
|
|
899
|
+
this.annotations[roiGraphic.uid].destroy();
|
|
900
|
+
delete this.annotations[roiGraphic.uid];
|
|
901
|
+
this._broadcastEvent(MicroscopyService_EVENTS.ANNOTATION_REMOVED, roiGraphic);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Observes any changes on ROI graphics and synchronize all the managed
|
|
906
|
+
* viewers to reflect those changes.
|
|
907
|
+
* Also publishes an ANNOTATION_UPDATED event to notify the subscribers.
|
|
908
|
+
*
|
|
909
|
+
* @param {Object} data The published data
|
|
910
|
+
* @param {Object} data.roiGraphic The added ROI graphic object
|
|
911
|
+
* @param {ViewerManager} data.managedViewer The origin viewer for the event
|
|
912
|
+
*/
|
|
913
|
+
_onRoiUpdated(data) {
|
|
914
|
+
const {
|
|
915
|
+
roiGraphic,
|
|
916
|
+
managedViewer
|
|
917
|
+
} = data;
|
|
918
|
+
this.synchronizeViewers(managedViewer);
|
|
919
|
+
this._broadcastEvent(MicroscopyService_EVENTS.ANNOTATION_UPDATED, this.getAnnotation(roiGraphic.uid));
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Observes when an ROI is selected.
|
|
924
|
+
* Also publishes an ANNOTATION_SELECTED event to notify the subscribers.
|
|
925
|
+
*
|
|
926
|
+
* @param {Object} data The published data
|
|
927
|
+
* @param {Object} data.roiGraphic The added ROI graphic object
|
|
928
|
+
* @param {ViewerManager} data.managedViewer The origin viewer for the event
|
|
929
|
+
*/
|
|
930
|
+
_onRoiSelected(data) {
|
|
931
|
+
const {
|
|
932
|
+
roiGraphic
|
|
933
|
+
} = data;
|
|
934
|
+
const selectedAnnotation = this.getAnnotation(roiGraphic.uid);
|
|
935
|
+
if (selectedAnnotation && selectedAnnotation !== this.getSelectedAnnotation()) {
|
|
936
|
+
if (this.selectedAnnotation) this.clearSelection();
|
|
937
|
+
this.selectedAnnotation = selectedAnnotation;
|
|
938
|
+
this._broadcastEvent(MicroscopyService_EVENTS.ANNOTATION_SELECTED, selectedAnnotation);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Creates the subscriptions for the managed viewer being added
|
|
944
|
+
*
|
|
945
|
+
* @param {ViewerManager} managedViewer The viewer being added
|
|
946
|
+
*/
|
|
947
|
+
_addManagedViewerSubscriptions(managedViewer) {
|
|
948
|
+
managedViewer._roiAddedSubscription = managedViewer.subscribe(EVENTS.ADDED, this._onRoiAdded);
|
|
949
|
+
managedViewer._roiModifiedSubscription = managedViewer.subscribe(EVENTS.MODIFIED, this._onRoiModified);
|
|
950
|
+
managedViewer._roiRemovedSubscription = managedViewer.subscribe(EVENTS.REMOVED, this._onRoiRemoved);
|
|
951
|
+
managedViewer._roiUpdatedSubscription = managedViewer.subscribe(EVENTS.UPDATED, this._onRoiUpdated);
|
|
952
|
+
managedViewer._roiSelectedSubscription = managedViewer.subscribe(EVENTS.UPDATED, this._onRoiSelected);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Removes the subscriptions for the managed viewer being removed
|
|
957
|
+
*
|
|
958
|
+
* @param {ViewerManager} managedViewer The viewer being removed
|
|
959
|
+
*/
|
|
960
|
+
_removeManagedViewerSubscriptions(managedViewer) {
|
|
961
|
+
managedViewer._roiAddedSubscription && managedViewer._roiAddedSubscription.unsubscribe();
|
|
962
|
+
managedViewer._roiModifiedSubscription && managedViewer._roiModifiedSubscription.unsubscribe();
|
|
963
|
+
managedViewer._roiRemovedSubscription && managedViewer._roiRemovedSubscription.unsubscribe();
|
|
964
|
+
managedViewer._roiUpdatedSubscription && managedViewer._roiUpdatedSubscription.unsubscribe();
|
|
965
|
+
managedViewer._roiSelectedSubscription && managedViewer._roiSelectedSubscription.unsubscribe();
|
|
966
|
+
managedViewer._roiAddedSubscription = null;
|
|
967
|
+
managedViewer._roiModifiedSubscription = null;
|
|
968
|
+
managedViewer._roiRemovedSubscription = null;
|
|
969
|
+
managedViewer._roiUpdatedSubscription = null;
|
|
970
|
+
managedViewer._roiSelectedSubscription = null;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Returns the managed viewers that are displaying the image with the given
|
|
975
|
+
* study and series UIDs
|
|
976
|
+
*
|
|
977
|
+
* @param {String} studyInstanceUID UID for the study
|
|
978
|
+
* @param {String} seriesInstanceUID UID for the series
|
|
979
|
+
*
|
|
980
|
+
* @returns {Array} The managed viewers for the given series UID
|
|
981
|
+
*/
|
|
982
|
+
_getManagedViewersForSeries(studyInstanceUID, seriesInstanceUID) {
|
|
983
|
+
const filter = managedViewer => managedViewer.studyInstanceUID === studyInstanceUID && managedViewer.seriesInstanceUID === seriesInstanceUID;
|
|
984
|
+
return Array.from(this.managedViewers).filter(filter);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Returns the managed viewers that are displaying the image with the given
|
|
989
|
+
* study UID
|
|
990
|
+
*
|
|
991
|
+
* @param {String} studyInstanceUID UID for the study
|
|
992
|
+
*
|
|
993
|
+
* @returns {Array} The managed viewers for the given series UID
|
|
994
|
+
*/
|
|
995
|
+
getManagedViewersForStudy(studyInstanceUID) {
|
|
996
|
+
const filter = managedViewer => managedViewer.studyInstanceUID === studyInstanceUID;
|
|
997
|
+
return Array.from(this.managedViewers).filter(filter);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Restores the created annotations for the viewer being added
|
|
1002
|
+
*
|
|
1003
|
+
* @param {ViewerManager} managedViewer The viewer being added
|
|
1004
|
+
*/
|
|
1005
|
+
_restoreAnnotations(managedViewer) {
|
|
1006
|
+
const {
|
|
1007
|
+
studyInstanceUID,
|
|
1008
|
+
seriesInstanceUID
|
|
1009
|
+
} = managedViewer;
|
|
1010
|
+
const annotations = this.getAnnotationsForSeries(studyInstanceUID, seriesInstanceUID);
|
|
1011
|
+
annotations.forEach(roiAnnotation => {
|
|
1012
|
+
managedViewer.addRoiGraphic(roiAnnotation.roiGraphic);
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Creates a managed viewer instance for the given thrid-party API's viewer.
|
|
1018
|
+
* Restores existing annotations for the given study/series.
|
|
1019
|
+
* Adds event subscriptions for the viewer being added.
|
|
1020
|
+
* Focuses the selected annotation when the viewer is being loaded into the
|
|
1021
|
+
* active viewport.
|
|
1022
|
+
*
|
|
1023
|
+
* @param {Object} viewer Third-party viewer API's object to be managed
|
|
1024
|
+
* @param {Number} viewportIndex The index of the viewport to load the viewer
|
|
1025
|
+
* @param {HTMLElement} container The DOM element where it will be renderd
|
|
1026
|
+
* @param {String} studyInstanceUID The study UID of the loaded image
|
|
1027
|
+
* @param {String} seriesInstanceUID The series UID of the loaded image
|
|
1028
|
+
* @param {Array} displaySets All displaySets related to the same StudyInstanceUID
|
|
1029
|
+
*
|
|
1030
|
+
* @returns {ViewerManager} managed viewer
|
|
1031
|
+
*/
|
|
1032
|
+
addViewer(viewer, viewportIndex, container, studyInstanceUID, seriesInstanceUID) {
|
|
1033
|
+
const managedViewer = new viewerManager(viewer, viewportIndex, container, studyInstanceUID, seriesInstanceUID);
|
|
1034
|
+
this._restoreAnnotations(managedViewer);
|
|
1035
|
+
viewer._manager = managedViewer;
|
|
1036
|
+
this.managedViewers.add(managedViewer);
|
|
1037
|
+
|
|
1038
|
+
// this._potentiallyLoadSR(studyInstanceUID, displaySets);
|
|
1039
|
+
this._addManagedViewerSubscriptions(managedViewer);
|
|
1040
|
+
if (this.pendingFocus) {
|
|
1041
|
+
this.pendingFocus = false;
|
|
1042
|
+
this.focusAnnotation(this.selectedAnnotation, viewportIndex);
|
|
1043
|
+
}
|
|
1044
|
+
return managedViewer;
|
|
1045
|
+
}
|
|
1046
|
+
_potentiallyLoadSR(StudyInstanceUID, displaySets) {
|
|
1047
|
+
const studyMetadata = core_src.DicomMetadataStore.getStudy(StudyInstanceUID);
|
|
1048
|
+
const smDisplaySet = displaySets.find(ds => ds.Modality === 'SM');
|
|
1049
|
+
const {
|
|
1050
|
+
FrameOfReferenceUID,
|
|
1051
|
+
othersFrameOfReferenceUID
|
|
1052
|
+
} = smDisplaySet;
|
|
1053
|
+
if (!studyMetadata) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
let derivedDisplaySets = FrameOfReferenceUID ? displaySets.filter(ds => ds.ReferencedFrameOfReferenceUID === FrameOfReferenceUID ||
|
|
1057
|
+
// sometimes each depth instance has the different FrameOfReferenceID
|
|
1058
|
+
othersFrameOfReferenceUID.includes(ds.ReferencedFrameOfReferenceUID)) : [];
|
|
1059
|
+
if (!derivedDisplaySets.length) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
derivedDisplaySets = derivedDisplaySets.filter(ds => ds.Modality === 'SR');
|
|
1063
|
+
if (derivedDisplaySets.some(ds => ds.isLoaded === true)) {
|
|
1064
|
+
// Don't auto load
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// find most recent and load it.
|
|
1069
|
+
let recentDateTime = 0;
|
|
1070
|
+
let recentDisplaySet = derivedDisplaySets[0];
|
|
1071
|
+
derivedDisplaySets.forEach(ds => {
|
|
1072
|
+
const dateTime = Number(`${ds.SeriesDate}${ds.SeriesTime}`);
|
|
1073
|
+
if (dateTime > recentDateTime) {
|
|
1074
|
+
recentDateTime = dateTime;
|
|
1075
|
+
recentDisplaySet = ds;
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
recentDisplaySet.isLoading = true;
|
|
1079
|
+
recentDisplaySet.load(smDisplaySet);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* Removes the given third-party viewer API's object from the managed viewers
|
|
1084
|
+
* and cleares all its event subscriptions
|
|
1085
|
+
*
|
|
1086
|
+
* @param {Object} viewer Third-party viewer API's object to be removed
|
|
1087
|
+
*/
|
|
1088
|
+
removeViewer(viewer) {
|
|
1089
|
+
const managedViewer = viewer._manager;
|
|
1090
|
+
this._removeManagedViewerSubscriptions(managedViewer);
|
|
1091
|
+
managedViewer.destroy();
|
|
1092
|
+
this.managedViewers.delete(managedViewer);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Toggle ROIs visibility
|
|
1097
|
+
*/
|
|
1098
|
+
toggleROIsVisibility() {
|
|
1099
|
+
this.isROIsVisible ? this.hideROIs() : this.showROIs;
|
|
1100
|
+
this.isROIsVisible = !this.isROIsVisible;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Hide all ROIs
|
|
1105
|
+
*/
|
|
1106
|
+
hideROIs() {
|
|
1107
|
+
this.managedViewers.forEach(mv => mv.hideROIs());
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/** Show all ROIs */
|
|
1111
|
+
showROIs() {
|
|
1112
|
+
this.managedViewers.forEach(mv => mv.showROIs());
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Returns a RoiAnnotation instance for the given ROI UID
|
|
1117
|
+
*
|
|
1118
|
+
* @param {String} uid UID of the annotation
|
|
1119
|
+
*
|
|
1120
|
+
* @returns {RoiAnnotation} The RoiAnnotation instance found for the given UID
|
|
1121
|
+
*/
|
|
1122
|
+
getAnnotation(uid) {
|
|
1123
|
+
return this.annotations[uid];
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Returns all the RoiAnnotation instances being managed
|
|
1128
|
+
*
|
|
1129
|
+
* @returns {Array} All RoiAnnotation instances
|
|
1130
|
+
*/
|
|
1131
|
+
getAnnotations() {
|
|
1132
|
+
const annotations = [];
|
|
1133
|
+
Object.keys(this.annotations).forEach(uid => {
|
|
1134
|
+
annotations.push(this.getAnnotation(uid));
|
|
1135
|
+
});
|
|
1136
|
+
return annotations;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Returns the RoiAnnotation instances registered with the given study UID
|
|
1141
|
+
*
|
|
1142
|
+
* @param {String} studyInstanceUID UID for the study
|
|
1143
|
+
*/
|
|
1144
|
+
getAnnotationsForStudy(studyInstanceUID) {
|
|
1145
|
+
const filter = a => a.studyInstanceUID === studyInstanceUID;
|
|
1146
|
+
return this.getAnnotations().filter(filter);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Returns the RoiAnnotation instances registered with the given study and
|
|
1151
|
+
* series UIDs
|
|
1152
|
+
*
|
|
1153
|
+
* @param {String} studyInstanceUID UID for the study
|
|
1154
|
+
* @param {String} seriesInstanceUID UID for the series
|
|
1155
|
+
*/
|
|
1156
|
+
getAnnotationsForSeries(studyInstanceUID, seriesInstanceUID) {
|
|
1157
|
+
const filter = annotation => annotation.studyInstanceUID === studyInstanceUID && annotation.seriesInstanceUID === seriesInstanceUID;
|
|
1158
|
+
return this.getAnnotations().filter(filter);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Returns the selected RoiAnnotation instance or null if none is selected
|
|
1163
|
+
*
|
|
1164
|
+
* @returns {RoiAnnotation} The selected RoiAnnotation instance
|
|
1165
|
+
*/
|
|
1166
|
+
getSelectedAnnotation() {
|
|
1167
|
+
return this.selectedAnnotation;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Clear current RoiAnnotation selection
|
|
1172
|
+
*/
|
|
1173
|
+
clearSelection() {
|
|
1174
|
+
if (this.selectedAnnotation) {
|
|
1175
|
+
this.setROIStyle(this.selectedAnnotation.uid, {
|
|
1176
|
+
stroke: {
|
|
1177
|
+
color: '#00ff00'
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
this.selectedAnnotation = null;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Selects the given RoiAnnotation instance, publishing an ANNOTATION_SELECTED
|
|
1186
|
+
* event to notify all the subscribers
|
|
1187
|
+
*
|
|
1188
|
+
* @param {RoiAnnotation} roiAnnotation The instance to be selected
|
|
1189
|
+
*/
|
|
1190
|
+
selectAnnotation(roiAnnotation) {
|
|
1191
|
+
if (this.selectedAnnotation) this.clearSelection();
|
|
1192
|
+
this.selectedAnnotation = roiAnnotation;
|
|
1193
|
+
this._broadcastEvent(MicroscopyService_EVENTS.ANNOTATION_SELECTED, roiAnnotation);
|
|
1194
|
+
this.setROIStyle(roiAnnotation.uid, utils_styles.active);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/**
|
|
1198
|
+
* Toggles overview map
|
|
1199
|
+
*
|
|
1200
|
+
* @param viewportIndex The active viewport index
|
|
1201
|
+
* @returns {void}
|
|
1202
|
+
*/
|
|
1203
|
+
toggleOverviewMap(viewportIndex) {
|
|
1204
|
+
const managedViewers = Array.from(this.managedViewers);
|
|
1205
|
+
const managedViewer = managedViewers.find(mv => mv.viewportIndex === viewportIndex);
|
|
1206
|
+
if (managedViewer) {
|
|
1207
|
+
managedViewer.toggleOverviewMap();
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Removes a RoiAnnotation instance from the managed annotations and reflects
|
|
1213
|
+
* its removal on all third-party viewers being managed
|
|
1214
|
+
*
|
|
1215
|
+
* @param {RoiAnnotation} roiAnnotation The instance to be removed
|
|
1216
|
+
*/
|
|
1217
|
+
removeAnnotation(roiAnnotation) {
|
|
1218
|
+
const {
|
|
1219
|
+
uid,
|
|
1220
|
+
studyInstanceUID,
|
|
1221
|
+
seriesInstanceUID
|
|
1222
|
+
} = roiAnnotation;
|
|
1223
|
+
const filter = managedViewer => managedViewer.studyInstanceUID === studyInstanceUID && managedViewer.seriesInstanceUID === seriesInstanceUID;
|
|
1224
|
+
const managedViewers = Array.from(this.managedViewers).filter(filter);
|
|
1225
|
+
managedViewers.forEach(managedViewer => managedViewer.removeRoiGraphic(uid));
|
|
1226
|
+
if (this.annotations[uid]) {
|
|
1227
|
+
this.roiUids.delete(uid);
|
|
1228
|
+
this.annotations[uid].destroy();
|
|
1229
|
+
delete this.annotations[uid];
|
|
1230
|
+
this._broadcastEvent(MicroscopyService_EVENTS.ANNOTATION_REMOVED, roiAnnotation);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* Focus the given RoiAnnotation instance by changing the OpenLayers' Map view
|
|
1236
|
+
* state of the managed viewer with the given viewport index.
|
|
1237
|
+
* If the image for the given annotation is not yet loaded into the viewport,
|
|
1238
|
+
* it will set a pendingFocus flag to true in order to perform the focus when
|
|
1239
|
+
* the managed viewer instance is created.
|
|
1240
|
+
*
|
|
1241
|
+
* @param {RoiAnnotation} roiAnnotation RoiAnnotation instance to be focused
|
|
1242
|
+
* @param {Number} viewportIndex Index of the viewport to focus
|
|
1243
|
+
*/
|
|
1244
|
+
focusAnnotation(roiAnnotation, viewportIndex) {
|
|
1245
|
+
const filter = mv => mv.viewportIndex === viewportIndex;
|
|
1246
|
+
const managedViewer = Array.from(this.managedViewers).find(filter);
|
|
1247
|
+
if (managedViewer) {
|
|
1248
|
+
managedViewer.setViewStateByExtent(roiAnnotation);
|
|
1249
|
+
} else {
|
|
1250
|
+
this.pendingFocus = true;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* Synchronize the ROI graphics for all the managed viewers that has the same
|
|
1256
|
+
* series UID of the given managed viewer
|
|
1257
|
+
*
|
|
1258
|
+
* @param {ViewerManager} baseManagedViewer Reference managed viewer
|
|
1259
|
+
*/
|
|
1260
|
+
synchronizeViewers(baseManagedViewer) {
|
|
1261
|
+
const {
|
|
1262
|
+
studyInstanceUID,
|
|
1263
|
+
seriesInstanceUID
|
|
1264
|
+
} = baseManagedViewer;
|
|
1265
|
+
const managedViewers = this._getManagedViewersForSeries(studyInstanceUID, seriesInstanceUID);
|
|
1266
|
+
|
|
1267
|
+
// Prevent infinite loops arrising from updates.
|
|
1268
|
+
managedViewers.forEach(managedViewer => this._removeManagedViewerSubscriptions(managedViewer));
|
|
1269
|
+
managedViewers.forEach(managedViewer => {
|
|
1270
|
+
if (managedViewer === baseManagedViewer) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const annotations = this.getAnnotationsForSeries(studyInstanceUID, seriesInstanceUID);
|
|
1274
|
+
managedViewer.clearRoiGraphics();
|
|
1275
|
+
annotations.forEach(roiAnnotation => {
|
|
1276
|
+
managedViewer.addRoiGraphic(roiAnnotation.roiGraphic);
|
|
1277
|
+
});
|
|
1278
|
+
});
|
|
1279
|
+
managedViewers.forEach(managedViewer => this._addManagedViewerSubscriptions(managedViewer));
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Activates interactions across all the viewers being managed
|
|
1284
|
+
*
|
|
1285
|
+
* @param {Array} interactions interactions
|
|
1286
|
+
*/
|
|
1287
|
+
activateInteractions(interactions) {
|
|
1288
|
+
this.managedViewers.forEach(mv => mv.activateInteractions(interactions));
|
|
1289
|
+
this.activeInteractions = interactions;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Triggers the relabelling process for the given RoiAnnotation instance, by
|
|
1294
|
+
* publishing the RELABEL event to notify the subscribers
|
|
1295
|
+
*
|
|
1296
|
+
* @param {RoiAnnotation} roiAnnotation The instance to be relabelled
|
|
1297
|
+
* @param {boolean} newAnnotation Whether the annotation is newly drawn (so it deletes on cancel).
|
|
1298
|
+
*/
|
|
1299
|
+
triggerRelabel(roiAnnotation) {
|
|
1300
|
+
let newAnnotation = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
1301
|
+
let onRelabel = arguments.length > 2 ? arguments[2] : undefined;
|
|
1302
|
+
if (!onRelabel) {
|
|
1303
|
+
onRelabel = _ref => {
|
|
1304
|
+
let {
|
|
1305
|
+
label
|
|
1306
|
+
} = _ref;
|
|
1307
|
+
return this.managedViewers.forEach(mv => mv.updateROIProperties({
|
|
1308
|
+
uid: roiAnnotation.uid,
|
|
1309
|
+
properties: {
|
|
1310
|
+
label
|
|
1311
|
+
}
|
|
1312
|
+
}));
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
this._broadcastEvent(MicroscopyService_EVENTS.RELABEL, {
|
|
1316
|
+
roiAnnotation,
|
|
1317
|
+
deleteCallback: () => this.removeAnnotation(roiAnnotation),
|
|
1318
|
+
successCallback: onRelabel,
|
|
1319
|
+
newAnnotation
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Triggers the deletion process for the given RoiAnnotation instance, by
|
|
1325
|
+
* publishing the DELETE event to notify the subscribers
|
|
1326
|
+
*
|
|
1327
|
+
* @param {RoiAnnotation} roiAnnotation The instance to be deleted
|
|
1328
|
+
*/
|
|
1329
|
+
triggerDelete(roiAnnotation) {
|
|
1330
|
+
this._broadcastEvent(MicroscopyService_EVENTS.DELETE, roiAnnotation);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Set ROI style for all managed viewers
|
|
1335
|
+
*
|
|
1336
|
+
* @param {string} uid The ROI uid that will be styled
|
|
1337
|
+
* @param {object} styleOptions - Style options
|
|
1338
|
+
* @param {object*} styleOptions.stroke - Style options for the outline of the geometry
|
|
1339
|
+
* @param {number[]} styleOptions.stroke.color - RGBA color of the outline
|
|
1340
|
+
* @param {number} styleOptions.stroke.width - Width of the outline
|
|
1341
|
+
* @param {object*} styleOptions.fill - Style options for body the geometry
|
|
1342
|
+
* @param {number[]} styleOptions.fill.color - RGBA color of the body
|
|
1343
|
+
* @param {object*} styleOptions.image - Style options for image
|
|
1344
|
+
*/
|
|
1345
|
+
setROIStyle(uid, styleOptions) {
|
|
1346
|
+
this.managedViewers.forEach(mv => mv.setROIStyle(uid, styleOptions));
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
MicroscopyService.REGISTRATION = serviceManager => {
|
|
1350
|
+
return {
|
|
1351
|
+
name: 'microscopyService',
|
|
1352
|
+
altName: 'MicroscopyService',
|
|
1353
|
+
create: _ref2 => {
|
|
1354
|
+
let {
|
|
1355
|
+
configuration = {}
|
|
1356
|
+
} = _ref2;
|
|
1357
|
+
return new MicroscopyService(serviceManager);
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
// EXTERNAL MODULE: ../../../node_modules/dcmjs/build/dcmjs.es.js
|
|
1363
|
+
var dcmjs_es = __webpack_require__(22737);
|
|
1364
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/callInputDialog.tsx
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
*
|
|
1370
|
+
* @param {*} data
|
|
1371
|
+
* @param {*} data.text
|
|
1372
|
+
* @param {*} data.label
|
|
1373
|
+
* @param {*} event
|
|
1374
|
+
* @param {func} callback
|
|
1375
|
+
* @param {*} isArrowAnnotateInputDialog
|
|
1376
|
+
*/
|
|
1377
|
+
function callInputDialog(_ref) {
|
|
1378
|
+
let {
|
|
1379
|
+
uiDialogService,
|
|
1380
|
+
title = 'Enter your annotation',
|
|
1381
|
+
defaultValue = '',
|
|
1382
|
+
callback = (value, action) => {}
|
|
1383
|
+
} = _ref;
|
|
1384
|
+
const dialogId = 'microscopy-input-dialog';
|
|
1385
|
+
const onSubmitHandler = _ref2 => {
|
|
1386
|
+
let {
|
|
1387
|
+
action,
|
|
1388
|
+
value
|
|
1389
|
+
} = _ref2;
|
|
1390
|
+
switch (action.id) {
|
|
1391
|
+
case 'save':
|
|
1392
|
+
callback(value.value, action.id);
|
|
1393
|
+
break;
|
|
1394
|
+
case 'cancel':
|
|
1395
|
+
callback('', action.id);
|
|
1396
|
+
break;
|
|
1397
|
+
}
|
|
1398
|
+
uiDialogService.dismiss({
|
|
1399
|
+
id: dialogId
|
|
1400
|
+
});
|
|
1401
|
+
};
|
|
1402
|
+
if (uiDialogService) {
|
|
1403
|
+
uiDialogService.create({
|
|
1404
|
+
id: dialogId,
|
|
1405
|
+
centralize: true,
|
|
1406
|
+
isDraggable: false,
|
|
1407
|
+
showOverlay: true,
|
|
1408
|
+
content: src/* Dialog */.Vq,
|
|
1409
|
+
contentProps: {
|
|
1410
|
+
title: title,
|
|
1411
|
+
value: {
|
|
1412
|
+
value: defaultValue
|
|
1413
|
+
},
|
|
1414
|
+
noCloseButton: true,
|
|
1415
|
+
onClose: () => uiDialogService.dismiss({
|
|
1416
|
+
id: dialogId
|
|
1417
|
+
}),
|
|
1418
|
+
actions: [{
|
|
1419
|
+
id: 'cancel',
|
|
1420
|
+
text: 'Cancel',
|
|
1421
|
+
type: 'primary'
|
|
1422
|
+
}, {
|
|
1423
|
+
id: 'save',
|
|
1424
|
+
text: 'Save',
|
|
1425
|
+
type: 'secondary'
|
|
1426
|
+
}],
|
|
1427
|
+
onSubmit: onSubmitHandler,
|
|
1428
|
+
body: _ref3 => {
|
|
1429
|
+
let {
|
|
1430
|
+
value,
|
|
1431
|
+
setValue
|
|
1432
|
+
} = _ref3;
|
|
1433
|
+
return /*#__PURE__*/react.createElement("div", {
|
|
1434
|
+
className: "p-4 bg-primary-dark"
|
|
1435
|
+
}, /*#__PURE__*/react.createElement(src/* Input */.II, {
|
|
1436
|
+
autoFocus: true,
|
|
1437
|
+
className: "mt-2 bg-black border-primary-main",
|
|
1438
|
+
type: "text",
|
|
1439
|
+
containerClassName: "mr-2",
|
|
1440
|
+
value: value.defaultValue,
|
|
1441
|
+
onChange: event => {
|
|
1442
|
+
event.persist();
|
|
1443
|
+
setValue(value => ({
|
|
1444
|
+
...value,
|
|
1445
|
+
value: event.target.value
|
|
1446
|
+
}));
|
|
1447
|
+
},
|
|
1448
|
+
onKeyPress: event => {
|
|
1449
|
+
if (event.key === 'Enter') {
|
|
1450
|
+
onSubmitHandler({
|
|
1451
|
+
value,
|
|
1452
|
+
action: {
|
|
1453
|
+
id: 'save'
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}));
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/DEVICE_OBSERVER_UID.js
|
|
1465
|
+
// We need to define a UID for this extension as a device, and it should be the same for all saves:
|
|
1466
|
+
|
|
1467
|
+
const uid = '2.25.285241207697168520771311899641885187923';
|
|
1468
|
+
/* harmony default export */ const DEVICE_OBSERVER_UID = (uid);
|
|
1469
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/constructSR.ts
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
*
|
|
1475
|
+
* @param {*} metadata - Microscopy Image instance metadata
|
|
1476
|
+
* @param {*} SeriesDescription - SR description
|
|
1477
|
+
* @param {*} annotations - Annotations
|
|
1478
|
+
*
|
|
1479
|
+
* @return Comprehensive3DSR dataset
|
|
1480
|
+
*/
|
|
1481
|
+
function constructSR(metadata, _ref, annotations) {
|
|
1482
|
+
let {
|
|
1483
|
+
SeriesDescription,
|
|
1484
|
+
SeriesNumber
|
|
1485
|
+
} = _ref;
|
|
1486
|
+
// Handle malformed data
|
|
1487
|
+
if (!metadata.SpecimenDescriptionSequence) {
|
|
1488
|
+
metadata.SpecimenDescriptionSequence = {
|
|
1489
|
+
SpecimenUID: metadata.SeriesInstanceUID,
|
|
1490
|
+
SpecimenIdentifier: metadata.SeriesDescription
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
const {
|
|
1494
|
+
SpecimenDescriptionSequence
|
|
1495
|
+
} = metadata;
|
|
1496
|
+
|
|
1497
|
+
// construct Comprehensive3DSR dataset
|
|
1498
|
+
const observationContext = new dcmjs_es["default"].sr.templates.ObservationContext({
|
|
1499
|
+
observerPersonContext: new dcmjs_es["default"].sr.templates.ObserverContext({
|
|
1500
|
+
observerType: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1501
|
+
value: '121006',
|
|
1502
|
+
schemeDesignator: 'DCM',
|
|
1503
|
+
meaning: 'Person'
|
|
1504
|
+
}),
|
|
1505
|
+
observerIdentifyingAttributes: new dcmjs_es["default"].sr.templates.PersonObserverIdentifyingAttributes({
|
|
1506
|
+
name: '@ohif/extension-dicom-microscopy'
|
|
1507
|
+
})
|
|
1508
|
+
}),
|
|
1509
|
+
observerDeviceContext: new dcmjs_es["default"].sr.templates.ObserverContext({
|
|
1510
|
+
observerType: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1511
|
+
value: '121007',
|
|
1512
|
+
schemeDesignator: 'DCM',
|
|
1513
|
+
meaning: 'Device'
|
|
1514
|
+
}),
|
|
1515
|
+
observerIdentifyingAttributes: new dcmjs_es["default"].sr.templates.DeviceObserverIdentifyingAttributes({
|
|
1516
|
+
uid: DEVICE_OBSERVER_UID
|
|
1517
|
+
})
|
|
1518
|
+
}),
|
|
1519
|
+
subjectContext: new dcmjs_es["default"].sr.templates.SubjectContext({
|
|
1520
|
+
subjectClass: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1521
|
+
value: '121027',
|
|
1522
|
+
schemeDesignator: 'DCM',
|
|
1523
|
+
meaning: 'Specimen'
|
|
1524
|
+
}),
|
|
1525
|
+
subjectClassSpecificContext: new dcmjs_es["default"].sr.templates.SubjectContextSpecimen({
|
|
1526
|
+
uid: SpecimenDescriptionSequence.SpecimenUID,
|
|
1527
|
+
identifier: SpecimenDescriptionSequence.SpecimenIdentifier || metadata.SeriesInstanceUID,
|
|
1528
|
+
containerIdentifier: metadata.ContainerIdentifier || metadata.SeriesInstanceUID
|
|
1529
|
+
})
|
|
1530
|
+
})
|
|
1531
|
+
});
|
|
1532
|
+
const imagingMeasurements = [];
|
|
1533
|
+
for (let i = 0; i < annotations.length; i++) {
|
|
1534
|
+
const {
|
|
1535
|
+
roiGraphic: roi,
|
|
1536
|
+
label
|
|
1537
|
+
} = annotations[i];
|
|
1538
|
+
let {
|
|
1539
|
+
measurements,
|
|
1540
|
+
evaluations,
|
|
1541
|
+
marker,
|
|
1542
|
+
presentationState
|
|
1543
|
+
} = roi.properties;
|
|
1544
|
+
console.debug('[SR] storing marker...', marker);
|
|
1545
|
+
console.debug('[SR] storing measurements...', measurements);
|
|
1546
|
+
console.debug('[SR] storing evaluations...', evaluations);
|
|
1547
|
+
console.debug('[SR] storing presentation state...', presentationState);
|
|
1548
|
+
if (presentationState) presentationState.marker = marker;
|
|
1549
|
+
|
|
1550
|
+
/** Avoid incompatibility with dcmjs */
|
|
1551
|
+
measurements = measurements.map(measurement => {
|
|
1552
|
+
const ConceptName = Array.isArray(measurement.ConceptNameCodeSequence) ? measurement.ConceptNameCodeSequence[0] : measurement.ConceptNameCodeSequence;
|
|
1553
|
+
const MeasuredValue = Array.isArray(measurement.MeasuredValueSequence) ? measurement.MeasuredValueSequence[0] : measurement.MeasuredValueSequence;
|
|
1554
|
+
const MeasuredValueUnits = Array.isArray(MeasuredValue.MeasurementUnitsCodeSequence) ? MeasuredValue.MeasurementUnitsCodeSequence[0] : MeasuredValue.MeasurementUnitsCodeSequence;
|
|
1555
|
+
return new dcmjs_es["default"].sr.valueTypes.NumContentItem({
|
|
1556
|
+
name: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1557
|
+
meaning: ConceptName.CodeMeaning,
|
|
1558
|
+
value: ConceptName.CodeValue,
|
|
1559
|
+
schemeDesignator: ConceptName.CodingSchemeDesignator
|
|
1560
|
+
}),
|
|
1561
|
+
value: MeasuredValue.NumericValue,
|
|
1562
|
+
unit: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1563
|
+
value: MeasuredValueUnits.CodeValue,
|
|
1564
|
+
meaning: MeasuredValueUnits.CodeMeaning,
|
|
1565
|
+
schemeDesignator: MeasuredValueUnits.CodingSchemeDesignator
|
|
1566
|
+
})
|
|
1567
|
+
});
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
/** Avoid incompatibility with dcmjs */
|
|
1571
|
+
evaluations = evaluations.map(evaluation => {
|
|
1572
|
+
const ConceptName = Array.isArray(evaluation.ConceptNameCodeSequence) ? evaluation.ConceptNameCodeSequence[0] : evaluation.ConceptNameCodeSequence;
|
|
1573
|
+
return new dcmjs_es["default"].sr.valueTypes.TextContentItem({
|
|
1574
|
+
name: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1575
|
+
value: ConceptName.CodeValue,
|
|
1576
|
+
meaning: ConceptName.CodeMeaning,
|
|
1577
|
+
schemeDesignator: ConceptName.CodingSchemeDesignator
|
|
1578
|
+
}),
|
|
1579
|
+
value: evaluation.TextValue,
|
|
1580
|
+
relationshipType: evaluation.RelationshipType
|
|
1581
|
+
});
|
|
1582
|
+
});
|
|
1583
|
+
const identifier = `ROI #${i + 1}`;
|
|
1584
|
+
const group = new dcmjs_es["default"].sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations({
|
|
1585
|
+
trackingIdentifier: new dcmjs_es["default"].sr.templates.TrackingIdentifier({
|
|
1586
|
+
uid: roi.uid,
|
|
1587
|
+
identifier: presentationState ? identifier.concat(`(${JSON.stringify(presentationState)})`) : identifier
|
|
1588
|
+
}),
|
|
1589
|
+
referencedRegion: new dcmjs_es["default"].sr.contentItems.ImageRegion3D({
|
|
1590
|
+
graphicType: roi.scoord3d.graphicType,
|
|
1591
|
+
graphicData: roi.scoord3d.graphicData,
|
|
1592
|
+
frameOfReferenceUID: roi.scoord3d.frameOfReferenceUID
|
|
1593
|
+
}),
|
|
1594
|
+
findingType: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1595
|
+
value: label,
|
|
1596
|
+
schemeDesignator: '@ohif/extension-dicom-microscopy',
|
|
1597
|
+
meaning: 'FREETEXT'
|
|
1598
|
+
}),
|
|
1599
|
+
/** Evaluations will conflict with current tracking identifier */
|
|
1600
|
+
/** qualitativeEvaluations: evaluations, */
|
|
1601
|
+
measurements
|
|
1602
|
+
});
|
|
1603
|
+
imagingMeasurements.push(...group);
|
|
1604
|
+
}
|
|
1605
|
+
const measurementReport = new dcmjs_es["default"].sr.templates.MeasurementReport({
|
|
1606
|
+
languageOfContentItemAndDescendants: new dcmjs_es["default"].sr.templates.LanguageOfContentItemAndDescendants({}),
|
|
1607
|
+
observationContext,
|
|
1608
|
+
procedureReported: new dcmjs_es["default"].sr.coding.CodedConcept({
|
|
1609
|
+
value: '112703',
|
|
1610
|
+
schemeDesignator: 'DCM',
|
|
1611
|
+
meaning: 'Whole Slide Imaging'
|
|
1612
|
+
}),
|
|
1613
|
+
imagingMeasurements
|
|
1614
|
+
});
|
|
1615
|
+
const dataset = new dcmjs_es["default"].sr.documents.Comprehensive3DSR({
|
|
1616
|
+
content: measurementReport[0],
|
|
1617
|
+
evidence: [metadata],
|
|
1618
|
+
seriesInstanceUID: dcmjs_es["default"].data.DicomMetaDictionary.uid(),
|
|
1619
|
+
seriesNumber: SeriesNumber,
|
|
1620
|
+
seriesDescription: SeriesDescription || 'Whole slide imaging structured report',
|
|
1621
|
+
sopInstanceUID: dcmjs_es["default"].data.DicomMetaDictionary.uid(),
|
|
1622
|
+
instanceNumber: 1,
|
|
1623
|
+
manufacturer: 'dcmjs-org'
|
|
1624
|
+
});
|
|
1625
|
+
dataset.SpecificCharacterSet = 'ISO_IR 192';
|
|
1626
|
+
const fileMetaInformationVersionArray = new Uint8Array(2);
|
|
1627
|
+
fileMetaInformationVersionArray[1] = 1;
|
|
1628
|
+
dataset._meta = {
|
|
1629
|
+
FileMetaInformationVersion: {
|
|
1630
|
+
Value: [fileMetaInformationVersionArray.buffer],
|
|
1631
|
+
// TODO
|
|
1632
|
+
vr: 'OB'
|
|
1633
|
+
},
|
|
1634
|
+
MediaStorageSOPClassUID: dataset.sopClassUID,
|
|
1635
|
+
MediaStorageSOPInstanceUID: dataset.sopInstanceUID,
|
|
1636
|
+
TransferSyntaxUID: {
|
|
1637
|
+
Value: ['1.2.840.10008.1.2.1'],
|
|
1638
|
+
vr: 'UI'
|
|
1639
|
+
},
|
|
1640
|
+
ImplementationClassUID: {
|
|
1641
|
+
Value: [dcmjs_es["default"].data.DicomMetaDictionary.uid()],
|
|
1642
|
+
vr: 'UI'
|
|
1643
|
+
},
|
|
1644
|
+
ImplementationVersionName: {
|
|
1645
|
+
Value: ['@ohif/extension-dicom-microscopy'],
|
|
1646
|
+
vr: 'SH'
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
return dataset;
|
|
1650
|
+
}
|
|
1651
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/saveByteArray.ts
|
|
1652
|
+
/**
|
|
1653
|
+
* Trigger file download from an array buffer
|
|
1654
|
+
* @param buffer
|
|
1655
|
+
* @param filename
|
|
1656
|
+
*/
|
|
1657
|
+
function saveByteArray(buffer, filename) {
|
|
1658
|
+
var blob = new Blob([buffer], {
|
|
1659
|
+
type: 'application/dicom'
|
|
1660
|
+
});
|
|
1661
|
+
var link = document.createElement('a');
|
|
1662
|
+
link.href = window.URL.createObjectURL(blob);
|
|
1663
|
+
link.download = filename;
|
|
1664
|
+
link.click();
|
|
1665
|
+
}
|
|
1666
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
let saving = false;
|
|
1677
|
+
const {
|
|
1678
|
+
datasetToBuffer
|
|
1679
|
+
} = dcmjs_es["default"].data;
|
|
1680
|
+
const formatArea = area => {
|
|
1681
|
+
let mult = 1;
|
|
1682
|
+
let unit = 'mm';
|
|
1683
|
+
if (area > 1000000) {
|
|
1684
|
+
unit = 'm';
|
|
1685
|
+
mult = 1 / 1000000;
|
|
1686
|
+
} else if (area < 1) {
|
|
1687
|
+
unit = 'μm';
|
|
1688
|
+
mult = 1000000;
|
|
1689
|
+
}
|
|
1690
|
+
return `${(area * mult).toFixed(2)} ${unit}²`;
|
|
1691
|
+
};
|
|
1692
|
+
const formatLength = (length, unit) => {
|
|
1693
|
+
let mult = 1;
|
|
1694
|
+
if (unit == 'km' || !unit && length > 1000000) {
|
|
1695
|
+
unit = 'km';
|
|
1696
|
+
mult = 1 / 1000000;
|
|
1697
|
+
} else if (unit == 'm' || !unit && length > 1000) {
|
|
1698
|
+
unit = 'm';
|
|
1699
|
+
mult = 1 / 1000;
|
|
1700
|
+
} else if (unit == 'μm' || !unit && length < 1) {
|
|
1701
|
+
unit = 'μm';
|
|
1702
|
+
mult = 1000;
|
|
1703
|
+
} else if (unit && unit != 'mm') {
|
|
1704
|
+
throw new Error(`Unknown length unit ${unit}`);
|
|
1705
|
+
} else {
|
|
1706
|
+
unit = 'mm';
|
|
1707
|
+
}
|
|
1708
|
+
return `${(length * mult).toFixed(2)} ${unit}`;
|
|
1709
|
+
};
|
|
1710
|
+
/**
|
|
1711
|
+
* Microscopy Measurements Panel Component
|
|
1712
|
+
*
|
|
1713
|
+
* @param props
|
|
1714
|
+
* @returns
|
|
1715
|
+
*/
|
|
1716
|
+
function MicroscopyPanel(props) {
|
|
1717
|
+
const {
|
|
1718
|
+
microscopyService
|
|
1719
|
+
} = props.servicesManager.services;
|
|
1720
|
+
const [studyInstanceUID, setStudyInstanceUID] = (0,react.useState)(null);
|
|
1721
|
+
const [roiAnnotations, setRoiAnnotations] = (0,react.useState)([]);
|
|
1722
|
+
const [selectedAnnotation, setSelectedAnnotation] = (0,react.useState)(null);
|
|
1723
|
+
const {
|
|
1724
|
+
servicesManager,
|
|
1725
|
+
extensionManager
|
|
1726
|
+
} = props;
|
|
1727
|
+
const {
|
|
1728
|
+
uiDialogService,
|
|
1729
|
+
displaySetService
|
|
1730
|
+
} = servicesManager.services;
|
|
1731
|
+
(0,react.useEffect)(() => {
|
|
1732
|
+
const viewport = props.viewports[props.activeViewportIndex];
|
|
1733
|
+
if (viewport.displaySetInstanceUIDs[0]) {
|
|
1734
|
+
const displaySet = displaySetService.getDisplaySetByUID(viewport.displaySetInstanceUIDs[0]);
|
|
1735
|
+
if (displaySet) {
|
|
1736
|
+
setStudyInstanceUID(displaySet.StudyInstanceUID);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}, [props.viewports, props.activeViewportIndex]);
|
|
1740
|
+
(0,react.useEffect)(() => {
|
|
1741
|
+
const onAnnotationUpdated = () => {
|
|
1742
|
+
const roiAnnotations = microscopyService.getAnnotationsForStudy(studyInstanceUID);
|
|
1743
|
+
setRoiAnnotations(roiAnnotations);
|
|
1744
|
+
};
|
|
1745
|
+
const onAnnotationSelected = () => {
|
|
1746
|
+
const selectedAnnotation = microscopyService.getSelectedAnnotation();
|
|
1747
|
+
setSelectedAnnotation(selectedAnnotation);
|
|
1748
|
+
};
|
|
1749
|
+
const onAnnotationRemoved = () => {
|
|
1750
|
+
onAnnotationUpdated();
|
|
1751
|
+
};
|
|
1752
|
+
const {
|
|
1753
|
+
unsubscribe: unsubscribeAnnotationUpdated
|
|
1754
|
+
} = microscopyService.subscribe(MicroscopyService_EVENTS.ANNOTATION_UPDATED, onAnnotationUpdated);
|
|
1755
|
+
const {
|
|
1756
|
+
unsubscribe: unsubscribeAnnotationSelected
|
|
1757
|
+
} = microscopyService.subscribe(MicroscopyService_EVENTS.ANNOTATION_SELECTED, onAnnotationSelected);
|
|
1758
|
+
const {
|
|
1759
|
+
unsubscribe: unsubscribeAnnotationRemoved
|
|
1760
|
+
} = microscopyService.subscribe(MicroscopyService_EVENTS.ANNOTATION_REMOVED, onAnnotationRemoved);
|
|
1761
|
+
onAnnotationUpdated();
|
|
1762
|
+
onAnnotationSelected();
|
|
1763
|
+
|
|
1764
|
+
// on unload unsubscribe from events
|
|
1765
|
+
return () => {
|
|
1766
|
+
unsubscribeAnnotationUpdated();
|
|
1767
|
+
unsubscribeAnnotationSelected();
|
|
1768
|
+
unsubscribeAnnotationRemoved();
|
|
1769
|
+
};
|
|
1770
|
+
}, [studyInstanceUID]);
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* On clicking "Save Annotations" button, prompt an input modal for the
|
|
1774
|
+
* new series' description, and continue to save.
|
|
1775
|
+
*
|
|
1776
|
+
* @returns
|
|
1777
|
+
*/
|
|
1778
|
+
const promptSave = () => {
|
|
1779
|
+
const annotations = microscopyService.getAnnotationsForStudy(studyInstanceUID);
|
|
1780
|
+
if (!annotations || saving) {
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
callInputDialog({
|
|
1784
|
+
uiDialogService,
|
|
1785
|
+
title: 'Enter description of the Series',
|
|
1786
|
+
defaultValue: '',
|
|
1787
|
+
callback: (value, action) => {
|
|
1788
|
+
switch (action) {
|
|
1789
|
+
case 'save':
|
|
1790
|
+
{
|
|
1791
|
+
saveFunction(value);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
};
|
|
1797
|
+
const getAllDisplaySets = studyMetadata => {
|
|
1798
|
+
let allDisplaySets = [];
|
|
1799
|
+
studyMetadata.series.forEach(series => {
|
|
1800
|
+
const displaySets = displaySetService.getDisplaySetsForSeries(series.SeriesInstanceUID);
|
|
1801
|
+
allDisplaySets = allDisplaySets.concat(displaySets);
|
|
1802
|
+
});
|
|
1803
|
+
return allDisplaySets;
|
|
1804
|
+
};
|
|
1805
|
+
|
|
1806
|
+
/**
|
|
1807
|
+
* Save annotations as a series
|
|
1808
|
+
*
|
|
1809
|
+
* @param SeriesDescription - series description
|
|
1810
|
+
* @returns
|
|
1811
|
+
*/
|
|
1812
|
+
const saveFunction = async SeriesDescription => {
|
|
1813
|
+
const dataSource = extensionManager.getActiveDataSource()[0];
|
|
1814
|
+
const {
|
|
1815
|
+
onSaveComplete
|
|
1816
|
+
} = props;
|
|
1817
|
+
const annotations = microscopyService.getAnnotationsForStudy(studyInstanceUID);
|
|
1818
|
+
saving = true;
|
|
1819
|
+
|
|
1820
|
+
// There is only one viewer possible for one study,
|
|
1821
|
+
// Since once study contains multiple resolution levels (series) of one whole
|
|
1822
|
+
// Slide image.
|
|
1823
|
+
|
|
1824
|
+
const studyMetadata = core_src.DicomMetadataStore.getStudy(studyInstanceUID);
|
|
1825
|
+
const displaySets = getAllDisplaySets(studyMetadata);
|
|
1826
|
+
const smDisplaySet = displaySets.find(ds => ds.Modality === 'SM');
|
|
1827
|
+
|
|
1828
|
+
// Get the next available series number after 4700.
|
|
1829
|
+
|
|
1830
|
+
const dsWithMetadata = displaySets.filter(ds => ds.metadata && ds.metadata.SeriesNumber && typeof ds.metadata.SeriesNumber === 'number');
|
|
1831
|
+
|
|
1832
|
+
// Generate next series number
|
|
1833
|
+
const seriesNumbers = dsWithMetadata.map(ds => ds.metadata.SeriesNumber);
|
|
1834
|
+
const maxSeriesNumber = Math.max(...seriesNumbers, 4700);
|
|
1835
|
+
const SeriesNumber = maxSeriesNumber + 1;
|
|
1836
|
+
const {
|
|
1837
|
+
instance: metadata
|
|
1838
|
+
} = smDisplaySet;
|
|
1839
|
+
|
|
1840
|
+
// construct SR dataset
|
|
1841
|
+
const dataset = constructSR(metadata, {
|
|
1842
|
+
SeriesDescription,
|
|
1843
|
+
SeriesNumber
|
|
1844
|
+
}, annotations);
|
|
1845
|
+
|
|
1846
|
+
// Save in DICOM format
|
|
1847
|
+
try {
|
|
1848
|
+
if (dataSource) {
|
|
1849
|
+
if (dataSource.wadoRoot == 'saveDicom') {
|
|
1850
|
+
// download as DICOM file
|
|
1851
|
+
const part10Buffer = datasetToBuffer(dataset);
|
|
1852
|
+
saveByteArray(part10Buffer, `sr-microscopy.dcm`);
|
|
1853
|
+
} else {
|
|
1854
|
+
// Save into Web Data source
|
|
1855
|
+
const {
|
|
1856
|
+
StudyInstanceUID
|
|
1857
|
+
} = dataset;
|
|
1858
|
+
await dataSource.store.dicom(dataset);
|
|
1859
|
+
if (StudyInstanceUID) {
|
|
1860
|
+
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
onSaveComplete({
|
|
1864
|
+
title: 'SR Saved',
|
|
1865
|
+
meassage: 'Measurements downloaded successfully',
|
|
1866
|
+
type: 'success'
|
|
1867
|
+
});
|
|
1868
|
+
} else {
|
|
1869
|
+
console.error('Server unspecified');
|
|
1870
|
+
}
|
|
1871
|
+
} catch (error) {
|
|
1872
|
+
onSaveComplete({
|
|
1873
|
+
title: 'SR Save Failed',
|
|
1874
|
+
message: error.message || error.toString(),
|
|
1875
|
+
type: 'error'
|
|
1876
|
+
});
|
|
1877
|
+
} finally {
|
|
1878
|
+
saving = false;
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
|
|
1882
|
+
/**
|
|
1883
|
+
* On clicking "Reject annotations" button
|
|
1884
|
+
*/
|
|
1885
|
+
const onDeleteCurrentSRHandler = async () => {
|
|
1886
|
+
try {
|
|
1887
|
+
const activeViewport = props.viewports[props.activeViewportIndex];
|
|
1888
|
+
const {
|
|
1889
|
+
StudyInstanceUID
|
|
1890
|
+
} = activeViewport;
|
|
1891
|
+
|
|
1892
|
+
// TODO: studies?
|
|
1893
|
+
const study = core_src.DicomMetadataStore.getStudy(StudyInstanceUID);
|
|
1894
|
+
const lastDerivedDisplaySet = study.derivedDisplaySets.sort((ds1, ds2) => {
|
|
1895
|
+
const dateTime1 = Number(`${ds1.SeriesDate}${ds1.SeriesTime}`);
|
|
1896
|
+
const dateTime2 = Number(`${ds2.SeriesDate}${ds2.SeriesTime}`);
|
|
1897
|
+
return dateTime1 > dateTime2;
|
|
1898
|
+
})[study.derivedDisplaySets.length - 1];
|
|
1899
|
+
|
|
1900
|
+
// TODO: use dataSource.reject.dicom()
|
|
1901
|
+
// await DICOMSR.rejectMeasurements(
|
|
1902
|
+
// study.wadoRoot,
|
|
1903
|
+
// lastDerivedDisplaySet.StudyInstanceUID,
|
|
1904
|
+
// lastDerivedDisplaySet.SeriesInstanceUID
|
|
1905
|
+
// );
|
|
1906
|
+
props.onRejectComplete({
|
|
1907
|
+
title: 'Report rejected',
|
|
1908
|
+
message: 'Latest report rejected successfully',
|
|
1909
|
+
type: 'success'
|
|
1910
|
+
});
|
|
1911
|
+
} catch (error) {
|
|
1912
|
+
props.onRejectComplete({
|
|
1913
|
+
title: 'Failed to reject report',
|
|
1914
|
+
message: error.message,
|
|
1915
|
+
type: 'error'
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
|
|
1920
|
+
/**
|
|
1921
|
+
* Handler for clicking event of an annotation item.
|
|
1922
|
+
*
|
|
1923
|
+
* @param param0
|
|
1924
|
+
*/
|
|
1925
|
+
const onMeasurementItemClickHandler = _ref => {
|
|
1926
|
+
let {
|
|
1927
|
+
uid
|
|
1928
|
+
} = _ref;
|
|
1929
|
+
const roiAnnotation = microscopyService.getAnnotation(uid);
|
|
1930
|
+
microscopyService.selectAnnotation(roiAnnotation);
|
|
1931
|
+
microscopyService.focusAnnotation(roiAnnotation, props.activeViewportIndex);
|
|
1932
|
+
};
|
|
1933
|
+
|
|
1934
|
+
/**
|
|
1935
|
+
* Handler for "Edit" action of an annotation item
|
|
1936
|
+
* @param param0
|
|
1937
|
+
*/
|
|
1938
|
+
const onMeasurementItemEditHandler = _ref2 => {
|
|
1939
|
+
let {
|
|
1940
|
+
uid,
|
|
1941
|
+
isActive
|
|
1942
|
+
} = _ref2;
|
|
1943
|
+
props.commandsManager.runCommand('setLabel', {
|
|
1944
|
+
uid
|
|
1945
|
+
}, 'MICROSCOPY');
|
|
1946
|
+
};
|
|
1947
|
+
|
|
1948
|
+
// Convert ROI annotations managed by microscopyService into our
|
|
1949
|
+
// own format for display
|
|
1950
|
+
const data = roiAnnotations.map((roiAnnotation, index) => {
|
|
1951
|
+
const label = roiAnnotation.getDetailedLabel();
|
|
1952
|
+
const area = roiAnnotation.getArea();
|
|
1953
|
+
const length = roiAnnotation.getLength();
|
|
1954
|
+
const shortAxisLength = roiAnnotation.roiGraphic.properties.shortAxisLength;
|
|
1955
|
+
const isSelected = selectedAnnotation === roiAnnotation;
|
|
1956
|
+
|
|
1957
|
+
// other events
|
|
1958
|
+
const {
|
|
1959
|
+
uid
|
|
1960
|
+
} = roiAnnotation;
|
|
1961
|
+
|
|
1962
|
+
// display text
|
|
1963
|
+
const displayText = [];
|
|
1964
|
+
if (area !== undefined) {
|
|
1965
|
+
displayText.push(formatArea(area));
|
|
1966
|
+
} else if (length !== undefined) {
|
|
1967
|
+
displayText.push(shortAxisLength ? `${formatLength(length, 'μm')} x ${formatLength(shortAxisLength, 'μm')}` : `${formatLength(length, 'μm')}`);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// convert to measurementItem format compatible with <MeasurementTable /> component
|
|
1971
|
+
return {
|
|
1972
|
+
uid,
|
|
1973
|
+
index,
|
|
1974
|
+
label,
|
|
1975
|
+
isActive: isSelected,
|
|
1976
|
+
displayText,
|
|
1977
|
+
roiAnnotation
|
|
1978
|
+
};
|
|
1979
|
+
});
|
|
1980
|
+
const disabled = data.length === 0;
|
|
1981
|
+
return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
|
|
1982
|
+
className: "overflow-x-hidden overflow-y-auto ohif-scrollbar",
|
|
1983
|
+
"data-cy": 'measurements-panel'
|
|
1984
|
+
}, /*#__PURE__*/react.createElement(src/* MeasurementTable */.wt, {
|
|
1985
|
+
title: "Measurements",
|
|
1986
|
+
servicesManager: props.servicesManager,
|
|
1987
|
+
data: data,
|
|
1988
|
+
onClick: onMeasurementItemClickHandler,
|
|
1989
|
+
onEdit: onMeasurementItemEditHandler
|
|
1990
|
+
})), /*#__PURE__*/react.createElement("div", {
|
|
1991
|
+
className: "flex justify-center p-4"
|
|
1992
|
+
}, /*#__PURE__*/react.createElement(src/* ButtonGroup */.hE, {
|
|
1993
|
+
color: "black",
|
|
1994
|
+
size: "inherit"
|
|
1995
|
+
})));
|
|
1996
|
+
}
|
|
1997
|
+
const connectedMicroscopyPanel = (0,es/* withTranslation */.Zh)(['MicroscopyTable', 'Common'])(MicroscopyPanel);
|
|
1998
|
+
/* harmony default export */ const MicroscopyPanel_MicroscopyPanel = (connectedMicroscopyPanel);
|
|
1999
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/getPanelModule.tsx
|
|
2000
|
+
|
|
2001
|
+
|
|
2002
|
+
|
|
2003
|
+
|
|
2004
|
+
// TODO:
|
|
2005
|
+
// - No loading UI exists yet
|
|
2006
|
+
// - cancel promises when component is destroyed
|
|
2007
|
+
// - show errors in UI for thumbnails if promise fails
|
|
2008
|
+
|
|
2009
|
+
function getPanelModule(_ref) {
|
|
2010
|
+
let {
|
|
2011
|
+
commandsManager,
|
|
2012
|
+
extensionManager,
|
|
2013
|
+
servicesManager
|
|
2014
|
+
} = _ref;
|
|
2015
|
+
const wrappedMeasurementPanel = () => {
|
|
2016
|
+
const [{
|
|
2017
|
+
activeViewportIndex,
|
|
2018
|
+
viewports
|
|
2019
|
+
}, viewportGridService] = (0,src/* useViewportGrid */.O_)();
|
|
2020
|
+
return /*#__PURE__*/react.createElement(MicroscopyPanel_MicroscopyPanel, {
|
|
2021
|
+
viewports: viewports,
|
|
2022
|
+
activeViewportIndex: activeViewportIndex,
|
|
2023
|
+
onSaveComplete: () => {},
|
|
2024
|
+
onRejectComplete: () => {},
|
|
2025
|
+
commandsManager: commandsManager,
|
|
2026
|
+
servicesManager: servicesManager,
|
|
2027
|
+
extensionManager: extensionManager
|
|
2028
|
+
});
|
|
2029
|
+
};
|
|
2030
|
+
return [{
|
|
2031
|
+
name: 'measure',
|
|
2032
|
+
iconName: 'tab-linear',
|
|
2033
|
+
iconLabel: 'Measure',
|
|
2034
|
+
label: 'Measurements',
|
|
2035
|
+
secondaryLabel: 'Measurements',
|
|
2036
|
+
component: wrappedMeasurementPanel
|
|
2037
|
+
}];
|
|
2038
|
+
}
|
|
2039
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/getCommandsModule.ts
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
function getCommandsModule(_ref) {
|
|
2043
|
+
let {
|
|
2044
|
+
servicesManager,
|
|
2045
|
+
commandsManager,
|
|
2046
|
+
extensionManager
|
|
2047
|
+
} = _ref;
|
|
2048
|
+
const {
|
|
2049
|
+
viewportGridService,
|
|
2050
|
+
uiDialogService,
|
|
2051
|
+
microscopyService
|
|
2052
|
+
} = servicesManager.services;
|
|
2053
|
+
const actions = {
|
|
2054
|
+
// Measurement tool commands:
|
|
2055
|
+
deleteMeasurement: _ref2 => {
|
|
2056
|
+
let {
|
|
2057
|
+
uid
|
|
2058
|
+
} = _ref2;
|
|
2059
|
+
if (uid) {
|
|
2060
|
+
const roiAnnotation = microscopyService.getAnnotation(uid);
|
|
2061
|
+
if (roiAnnotation) microscopyService.removeAnnotation(roiAnnotation);
|
|
2062
|
+
}
|
|
2063
|
+
},
|
|
2064
|
+
setLabel: _ref3 => {
|
|
2065
|
+
let {
|
|
2066
|
+
uid
|
|
2067
|
+
} = _ref3;
|
|
2068
|
+
const roiAnnotation = microscopyService.getAnnotation(uid);
|
|
2069
|
+
callInputDialog({
|
|
2070
|
+
uiDialogService,
|
|
2071
|
+
title: 'Enter your annotation',
|
|
2072
|
+
defaultValue: '',
|
|
2073
|
+
callback: (value, action) => {
|
|
2074
|
+
switch (action) {
|
|
2075
|
+
case 'save':
|
|
2076
|
+
{
|
|
2077
|
+
roiAnnotation.setLabel(value);
|
|
2078
|
+
microscopyService.triggerRelabel(roiAnnotation);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
},
|
|
2084
|
+
setToolActive: _ref4 => {
|
|
2085
|
+
let {
|
|
2086
|
+
toolName,
|
|
2087
|
+
toolGroupId = 'MICROSCOPY'
|
|
2088
|
+
} = _ref4;
|
|
2089
|
+
const dragPanOnMiddle = ['dragPan', {
|
|
2090
|
+
bindings: {
|
|
2091
|
+
mouseButtons: ['middle']
|
|
2092
|
+
}
|
|
2093
|
+
}];
|
|
2094
|
+
const dragZoomOnRight = ['dragZoom', {
|
|
2095
|
+
bindings: {
|
|
2096
|
+
mouseButtons: ['right']
|
|
2097
|
+
}
|
|
2098
|
+
}];
|
|
2099
|
+
if (['line', 'box', 'circle', 'point', 'polygon', 'freehandpolygon', 'freehandline'].indexOf(toolName) >= 0) {
|
|
2100
|
+
// TODO: read from configuration
|
|
2101
|
+
let options = {
|
|
2102
|
+
geometryType: toolName,
|
|
2103
|
+
vertexEnabled: true,
|
|
2104
|
+
styleOptions: utils_styles["default"],
|
|
2105
|
+
bindings: {
|
|
2106
|
+
mouseButtons: ['left']
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
if ('line' === toolName) {
|
|
2110
|
+
options.minPoints = 2;
|
|
2111
|
+
options.maxPoints = 2;
|
|
2112
|
+
} else if ('point' === toolName) {
|
|
2113
|
+
delete options.styleOptions;
|
|
2114
|
+
delete options.vertexEnabled;
|
|
2115
|
+
}
|
|
2116
|
+
microscopyService.activateInteractions([['draw', options], dragPanOnMiddle, dragZoomOnRight]);
|
|
2117
|
+
} else if (toolName == 'dragPan') {
|
|
2118
|
+
microscopyService.activateInteractions([['dragPan', {
|
|
2119
|
+
bindings: {
|
|
2120
|
+
mouseButtons: ['left', 'middle']
|
|
2121
|
+
}
|
|
2122
|
+
}], dragZoomOnRight]);
|
|
2123
|
+
} else {
|
|
2124
|
+
microscopyService.activateInteractions([[toolName, {
|
|
2125
|
+
bindings: {
|
|
2126
|
+
mouseButtons: ['left']
|
|
2127
|
+
}
|
|
2128
|
+
}], dragPanOnMiddle, dragZoomOnRight]);
|
|
2129
|
+
}
|
|
2130
|
+
},
|
|
2131
|
+
incrementActiveViewport: () => {
|
|
2132
|
+
const {
|
|
2133
|
+
activeViewportIndex,
|
|
2134
|
+
viewports
|
|
2135
|
+
} = viewportGridService.getState();
|
|
2136
|
+
const nextViewportIndex = (activeViewportIndex + 1) % viewports.length;
|
|
2137
|
+
viewportGridService.setActiveViewportIndex(nextViewportIndex);
|
|
2138
|
+
},
|
|
2139
|
+
decrementActiveViewport: () => {
|
|
2140
|
+
const {
|
|
2141
|
+
activeViewportIndex,
|
|
2142
|
+
viewports
|
|
2143
|
+
} = viewportGridService.getState();
|
|
2144
|
+
const nextViewportIndex = (activeViewportIndex - 1 + viewports.length) % viewports.length;
|
|
2145
|
+
viewportGridService.setActiveViewportIndex(nextViewportIndex);
|
|
2146
|
+
},
|
|
2147
|
+
toggleOverlays: () => {
|
|
2148
|
+
// overlay
|
|
2149
|
+
const overlays = document.getElementsByClassName('microscopy-viewport-overlay');
|
|
2150
|
+
let onoff = false; // true if this will toggle on
|
|
2151
|
+
for (let i = 0; i < overlays.length; i++) {
|
|
2152
|
+
if (i === 0) onoff = overlays.item(0).classList.contains('hidden');
|
|
2153
|
+
overlays.item(i).classList.toggle('hidden');
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// overview
|
|
2157
|
+
const {
|
|
2158
|
+
activeViewportIndex,
|
|
2159
|
+
viewports
|
|
2160
|
+
} = viewportGridService.getState();
|
|
2161
|
+
microscopyService.toggleOverviewMap(activeViewportIndex);
|
|
2162
|
+
},
|
|
2163
|
+
toggleAnnotations: () => {
|
|
2164
|
+
microscopyService.toggleROIsVisibility();
|
|
2165
|
+
}
|
|
2166
|
+
};
|
|
2167
|
+
const definitions = {
|
|
2168
|
+
deleteMeasurement: {
|
|
2169
|
+
commandFn: actions.deleteMeasurement,
|
|
2170
|
+
storeContexts: [],
|
|
2171
|
+
options: {}
|
|
2172
|
+
},
|
|
2173
|
+
setLabel: {
|
|
2174
|
+
commandFn: actions.setLabel,
|
|
2175
|
+
storeContexts: [],
|
|
2176
|
+
options: {}
|
|
2177
|
+
},
|
|
2178
|
+
setToolActive: {
|
|
2179
|
+
commandFn: actions.setToolActive,
|
|
2180
|
+
storeContexts: [],
|
|
2181
|
+
options: {}
|
|
2182
|
+
},
|
|
2183
|
+
incrementActiveViewport: {
|
|
2184
|
+
commandFn: actions.incrementActiveViewport,
|
|
2185
|
+
storeContexts: []
|
|
2186
|
+
},
|
|
2187
|
+
decrementActiveViewport: {
|
|
2188
|
+
commandFn: actions.decrementActiveViewport,
|
|
2189
|
+
storeContexts: []
|
|
2190
|
+
},
|
|
2191
|
+
toggleOverlays: {
|
|
2192
|
+
commandFn: actions.toggleOverlays,
|
|
2193
|
+
storeContexts: [],
|
|
2194
|
+
options: {}
|
|
2195
|
+
},
|
|
2196
|
+
toggleAnnotations: {
|
|
2197
|
+
commandFn: actions.toggleAnnotations,
|
|
2198
|
+
storeContexts: [],
|
|
2199
|
+
options: {}
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
return {
|
|
2203
|
+
actions,
|
|
2204
|
+
definitions,
|
|
2205
|
+
defaultContext: 'MICROSCOPY'
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
|
|
2209
|
+
|
|
2210
|
+
const {
|
|
2211
|
+
utils
|
|
2212
|
+
} = core_src["default"];
|
|
2213
|
+
const SOP_CLASS_UIDS = {
|
|
2214
|
+
VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.6'
|
|
2215
|
+
};
|
|
2216
|
+
const SOPClassHandlerId = '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler';
|
|
2217
|
+
function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) {
|
|
2218
|
+
// If the series has no instances, stop here
|
|
2219
|
+
if (!instances || !instances.length) {
|
|
2220
|
+
throw new Error('No instances were provided');
|
|
2221
|
+
}
|
|
2222
|
+
const instance = instances[0];
|
|
2223
|
+
let singleFrameInstance = instance;
|
|
2224
|
+
let currentFrames = +singleFrameInstance.NumberOfFrames || 1;
|
|
2225
|
+
for (const instanceI of instances) {
|
|
2226
|
+
const framesI = +instanceI.NumberOfFrames || 1;
|
|
2227
|
+
if (framesI < currentFrames) {
|
|
2228
|
+
singleFrameInstance = instanceI;
|
|
2229
|
+
currentFrames = framesI;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
let imageIdForThumbnail = null;
|
|
2233
|
+
if (singleFrameInstance) {
|
|
2234
|
+
if (currentFrames == 1) {
|
|
2235
|
+
// Not all DICOM server implementations support thumbnail service,
|
|
2236
|
+
// So if we have a single-frame image, we will prefer it.
|
|
2237
|
+
imageIdForThumbnail = singleFrameInstance.imageId;
|
|
2238
|
+
}
|
|
2239
|
+
if (!imageIdForThumbnail) {
|
|
2240
|
+
// use the thumbnail service provided by DICOM server
|
|
2241
|
+
const dataSource = extensionManager.getActiveDataSource()[0];
|
|
2242
|
+
imageIdForThumbnail = dataSource.getImageIdsForInstance({
|
|
2243
|
+
instance: singleFrameInstance,
|
|
2244
|
+
thumbnail: true
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
const {
|
|
2249
|
+
FrameOfReferenceUID,
|
|
2250
|
+
SeriesDescription,
|
|
2251
|
+
ContentDate,
|
|
2252
|
+
ContentTime,
|
|
2253
|
+
SeriesNumber,
|
|
2254
|
+
StudyInstanceUID,
|
|
2255
|
+
SeriesInstanceUID,
|
|
2256
|
+
SOPInstanceUID,
|
|
2257
|
+
SOPClassUID
|
|
2258
|
+
} = instance;
|
|
2259
|
+
instances = instances.map(inst => {
|
|
2260
|
+
// NOTE: According to DICOM standard a series should have a FrameOfReferenceUID
|
|
2261
|
+
// When the Microscopy file was built by certain tool from multiple image files,
|
|
2262
|
+
// each instance's FrameOfReferenceUID is sometimes different.
|
|
2263
|
+
// Even though this means the file was not well formatted DICOM VL Whole Slide Microscopy Image,
|
|
2264
|
+
// the case is so often, so let's override this value manually here.
|
|
2265
|
+
//
|
|
2266
|
+
// https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.4.html#sect_C.7.4.1.1.1
|
|
2267
|
+
|
|
2268
|
+
inst.FrameOfReferenceUID = instance.FrameOfReferenceUID;
|
|
2269
|
+
return inst;
|
|
2270
|
+
});
|
|
2271
|
+
const othersFrameOfReferenceUID = instances.filter(v => v).map(inst => inst.FrameOfReferenceUID).filter((value, index, array) => array.indexOf(value) === index);
|
|
2272
|
+
if (othersFrameOfReferenceUID.length > 1) {
|
|
2273
|
+
console.warn('Expected FrameOfReferenceUID of difference instances within a series to be the same, found multiple different values', othersFrameOfReferenceUID);
|
|
2274
|
+
}
|
|
2275
|
+
const displaySet = {
|
|
2276
|
+
plugin: 'microscopy',
|
|
2277
|
+
Modality: 'SM',
|
|
2278
|
+
altImageText: 'Microscopy',
|
|
2279
|
+
displaySetInstanceUID: utils.guid(),
|
|
2280
|
+
SOPInstanceUID,
|
|
2281
|
+
SeriesInstanceUID,
|
|
2282
|
+
StudyInstanceUID,
|
|
2283
|
+
FrameOfReferenceUID,
|
|
2284
|
+
SOPClassHandlerId,
|
|
2285
|
+
SOPClassUID,
|
|
2286
|
+
SeriesDescription: SeriesDescription || 'Microscopy Data',
|
|
2287
|
+
// Map ContentDate/Time to SeriesTime for series list sorting.
|
|
2288
|
+
SeriesDate: ContentDate,
|
|
2289
|
+
SeriesTime: ContentTime,
|
|
2290
|
+
SeriesNumber,
|
|
2291
|
+
firstInstance: singleFrameInstance,
|
|
2292
|
+
// top level instance in the image Pyramid
|
|
2293
|
+
instance,
|
|
2294
|
+
numImageFrames: 0,
|
|
2295
|
+
numInstances: 1,
|
|
2296
|
+
imageIdForThumbnail,
|
|
2297
|
+
// thumbnail image
|
|
2298
|
+
others: instances,
|
|
2299
|
+
// all other level instances in the image Pyramid
|
|
2300
|
+
othersFrameOfReferenceUID
|
|
2301
|
+
};
|
|
2302
|
+
return [displaySet];
|
|
2303
|
+
}
|
|
2304
|
+
function getDicomMicroscopySopClassHandler(_ref) {
|
|
2305
|
+
let {
|
|
2306
|
+
servicesManager,
|
|
2307
|
+
extensionManager
|
|
2308
|
+
} = _ref;
|
|
2309
|
+
const getDisplaySetsFromSeries = instances => {
|
|
2310
|
+
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
|
2311
|
+
};
|
|
2312
|
+
return {
|
|
2313
|
+
name: 'DicomMicroscopySopClassHandler',
|
|
2314
|
+
sopClassUids: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE],
|
|
2315
|
+
getDisplaySetsFromSeries
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/dcmCodeValues.js
|
|
2319
|
+
const DCM_CODE_VALUES = {
|
|
2320
|
+
IMAGING_MEASUREMENTS: '126010',
|
|
2321
|
+
MEASUREMENT_GROUP: '125007',
|
|
2322
|
+
IMAGE_REGION: '111030',
|
|
2323
|
+
FINDING: '121071',
|
|
2324
|
+
TRACKING_UNIQUE_IDENTIFIER: '112039',
|
|
2325
|
+
LENGTH: '410668003',
|
|
2326
|
+
AREA: '42798000',
|
|
2327
|
+
SHORT_AXIS: 'G-A186',
|
|
2328
|
+
LONG_AXIS: 'G-A185',
|
|
2329
|
+
ELLIPSE_AREA: 'G-D7FE' // TODO: Remove this
|
|
2330
|
+
};
|
|
2331
|
+
|
|
2332
|
+
/* harmony default export */ const dcmCodeValues = (DCM_CODE_VALUES);
|
|
2333
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/toArray.js
|
|
2334
|
+
function toArray(item) {
|
|
2335
|
+
return Array.isArray(item) ? item : [item];
|
|
2336
|
+
}
|
|
2337
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/loadSR.js
|
|
2338
|
+
|
|
2339
|
+
|
|
2340
|
+
|
|
2341
|
+
const MeasurementReport = dcmjs_es["default"].adapters.DICOMMicroscopyViewer.MeasurementReport;
|
|
2342
|
+
|
|
2343
|
+
// Define as async so that it returns a promise, expected by the ViewportGrid
|
|
2344
|
+
async function loadSR(microscopyService, microscopySRDisplaySet, referencedDisplaySet) {
|
|
2345
|
+
const naturalizedDataset = microscopySRDisplaySet.metadata;
|
|
2346
|
+
const {
|
|
2347
|
+
StudyInstanceUID,
|
|
2348
|
+
FrameOfReferenceUID
|
|
2349
|
+
} = referencedDisplaySet;
|
|
2350
|
+
const managedViewers = microscopyService.getManagedViewersForStudy(StudyInstanceUID);
|
|
2351
|
+
if (!managedViewers || !managedViewers.length) {
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
microscopySRDisplaySet.isLoaded = true;
|
|
2355
|
+
const {
|
|
2356
|
+
rois,
|
|
2357
|
+
labels
|
|
2358
|
+
} = await _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID);
|
|
2359
|
+
const managedViewer = managedViewers[0];
|
|
2360
|
+
for (let i = 0; i < rois.length; i++) {
|
|
2361
|
+
// NOTE: When saving Microscopy SR, we are attaching identifier property
|
|
2362
|
+
// to each ROI, and when read for display, it is coming in as "TEXT"
|
|
2363
|
+
// evaluation.
|
|
2364
|
+
// As the Dicom Microscopy Viewer will override styles for "Text" evalutations
|
|
2365
|
+
// to hide all other geometries, we are going to manually remove that
|
|
2366
|
+
// evaluation item.
|
|
2367
|
+
const roi = rois[i];
|
|
2368
|
+
const roiSymbols = Object.getOwnPropertySymbols(roi);
|
|
2369
|
+
const _properties = roiSymbols.find(s => s.description === 'properties');
|
|
2370
|
+
const properties = roi[_properties];
|
|
2371
|
+
properties['evaluations'] = [];
|
|
2372
|
+
managedViewer.addRoiGraphicWithLabel(roi, labels[i]);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async function _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID) {
|
|
2376
|
+
const toolState = MeasurementReport.generateToolState(naturalizedDataset);
|
|
2377
|
+
const tools = Object.getOwnPropertyNames(toolState);
|
|
2378
|
+
const DICOMMicroscopyViewer = await __webpack_require__.e(/* import() | dicom-microscopy-viewer */ 18).then(__webpack_require__.t.bind(__webpack_require__, 78457, 23));
|
|
2379
|
+
const measurementGroupContentItems = _getMeasurementGroups(naturalizedDataset);
|
|
2380
|
+
const rois = [];
|
|
2381
|
+
const labels = [];
|
|
2382
|
+
tools.forEach(t => {
|
|
2383
|
+
const toolSpecificToolState = toolState[t];
|
|
2384
|
+
let scoord3d;
|
|
2385
|
+
const capsToolType = t.toUpperCase();
|
|
2386
|
+
const measurementGroupContentItemsForTool = measurementGroupContentItems.filter(mg => {
|
|
2387
|
+
const imageRegionContentItem = toArray(mg.ContentSequence).find(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.IMAGE_REGION);
|
|
2388
|
+
return imageRegionContentItem.GraphicType === capsToolType;
|
|
2389
|
+
});
|
|
2390
|
+
toolSpecificToolState.forEach((coordinates, index) => {
|
|
2391
|
+
const properties = {};
|
|
2392
|
+
const options = {
|
|
2393
|
+
coordinates,
|
|
2394
|
+
frameOfReferenceUID: FrameOfReferenceUID
|
|
2395
|
+
};
|
|
2396
|
+
if (t === 'Polygon') {
|
|
2397
|
+
scoord3d = new DICOMMicroscopyViewer.scoord3d.Polygon(options);
|
|
2398
|
+
} else if (t === 'Polyline') {
|
|
2399
|
+
scoord3d = new DICOMMicroscopyViewer.scoord3d.Polyline(options);
|
|
2400
|
+
} else if (t === 'Point') {
|
|
2401
|
+
scoord3d = new DICOMMicroscopyViewer.scoord3d.Point(options);
|
|
2402
|
+
} else if (t === 'Ellipse') {
|
|
2403
|
+
scoord3d = new DICOMMicroscopyViewer.scoord3d.Ellipse(options);
|
|
2404
|
+
} else {
|
|
2405
|
+
throw new Error('Unsupported tool type');
|
|
2406
|
+
}
|
|
2407
|
+
const measurementGroup = measurementGroupContentItemsForTool[index];
|
|
2408
|
+
const findingGroup = toArray(measurementGroup.ContentSequence).find(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.FINDING);
|
|
2409
|
+
const trackingGroup = toArray(measurementGroup.ContentSequence).find(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.TRACKING_UNIQUE_IDENTIFIER);
|
|
2410
|
+
|
|
2411
|
+
/**
|
|
2412
|
+
* Extract presentation state from tracking identifier.
|
|
2413
|
+
* Currently is stored in SR but should be stored in its tags.
|
|
2414
|
+
*/
|
|
2415
|
+
if (trackingGroup) {
|
|
2416
|
+
const regExp = /\(([^)]+)\)/;
|
|
2417
|
+
const matches = regExp.exec(trackingGroup.TextValue);
|
|
2418
|
+
if (matches && matches[1]) {
|
|
2419
|
+
properties.presentationState = JSON.parse(matches[1]);
|
|
2420
|
+
properties.marker = properties.presentationState.marker;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
let measurements = toArray(measurementGroup.ContentSequence).filter(ci => [dcmCodeValues.LENGTH, dcmCodeValues.AREA, dcmCodeValues.SHORT_AXIS, dcmCodeValues.LONG_AXIS, dcmCodeValues.ELLIPSE_AREA].includes(ci.ConceptNameCodeSequence.CodeValue));
|
|
2424
|
+
let evaluations = toArray(measurementGroup.ContentSequence).filter(ci => [dcmCodeValues.TRACKING_UNIQUE_IDENTIFIER].includes(ci.ConceptNameCodeSequence.CodeValue));
|
|
2425
|
+
|
|
2426
|
+
/**
|
|
2427
|
+
* TODO: Resolve bug in DCMJS.
|
|
2428
|
+
* ConceptNameCodeSequence should be a sequence with only one item.
|
|
2429
|
+
*/
|
|
2430
|
+
evaluations = evaluations.map(evaluation => {
|
|
2431
|
+
const e = {
|
|
2432
|
+
...evaluation
|
|
2433
|
+
};
|
|
2434
|
+
e.ConceptNameCodeSequence = toArray(e.ConceptNameCodeSequence);
|
|
2435
|
+
return e;
|
|
2436
|
+
});
|
|
2437
|
+
|
|
2438
|
+
/**
|
|
2439
|
+
* TODO: Resolve bug in DCMJS.
|
|
2440
|
+
* ConceptNameCodeSequence should be a sequence with only one item.
|
|
2441
|
+
*/
|
|
2442
|
+
measurements = measurements.map(measurement => {
|
|
2443
|
+
const m = {
|
|
2444
|
+
...measurement
|
|
2445
|
+
};
|
|
2446
|
+
m.ConceptNameCodeSequence = toArray(m.ConceptNameCodeSequence);
|
|
2447
|
+
return m;
|
|
2448
|
+
});
|
|
2449
|
+
if (measurements && measurements.length) {
|
|
2450
|
+
properties.measurements = measurements;
|
|
2451
|
+
console.debug('[SR] retrieving measurements...', measurements);
|
|
2452
|
+
}
|
|
2453
|
+
if (evaluations && evaluations.length) {
|
|
2454
|
+
properties.evaluations = evaluations;
|
|
2455
|
+
console.debug('[SR] retrieving evaluations...', evaluations);
|
|
2456
|
+
}
|
|
2457
|
+
const roi = new DICOMMicroscopyViewer.roi.ROI({
|
|
2458
|
+
scoord3d,
|
|
2459
|
+
properties
|
|
2460
|
+
});
|
|
2461
|
+
rois.push(roi);
|
|
2462
|
+
if (findingGroup) {
|
|
2463
|
+
labels.push(findingGroup.ConceptCodeSequence.CodeValue);
|
|
2464
|
+
} else {
|
|
2465
|
+
labels.push('');
|
|
2466
|
+
}
|
|
2467
|
+
});
|
|
2468
|
+
});
|
|
2469
|
+
return {
|
|
2470
|
+
rois,
|
|
2471
|
+
labels
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
function _getMeasurementGroups(naturalizedDataset) {
|
|
2475
|
+
const {
|
|
2476
|
+
ContentSequence
|
|
2477
|
+
} = naturalizedDataset;
|
|
2478
|
+
const imagingMeasurementsContentItem = ContentSequence.find(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.IMAGING_MEASUREMENTS);
|
|
2479
|
+
const measurementGroupContentItems = toArray(imagingMeasurementsContentItem.ContentSequence).filter(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.MEASUREMENT_GROUP);
|
|
2480
|
+
return measurementGroupContentItems;
|
|
2481
|
+
}
|
|
2482
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
|
|
2483
|
+
/**
|
|
2484
|
+
* Get referenced SM displaySet from SR displaySet
|
|
2485
|
+
*
|
|
2486
|
+
* @param {*} allDisplaySets
|
|
2487
|
+
* @param {*} microscopySRDisplaySet
|
|
2488
|
+
* @returns
|
|
2489
|
+
*/
|
|
2490
|
+
function getSourceDisplaySet(allDisplaySets, microscopySRDisplaySet) {
|
|
2491
|
+
const {
|
|
2492
|
+
ReferencedFrameOfReferenceUID
|
|
2493
|
+
} = microscopySRDisplaySet;
|
|
2494
|
+
const otherDisplaySets = allDisplaySets.filter(ds => ds.displaySetInstanceUID !== microscopySRDisplaySet.displaySetInstanceUID);
|
|
2495
|
+
const referencedDisplaySet = otherDisplaySets.find(displaySet => displaySet.Modality === 'SM' && (displaySet.FrameOfReferenceUID === ReferencedFrameOfReferenceUID ||
|
|
2496
|
+
// sometimes each depth instance has the different FrameOfReferenceID
|
|
2497
|
+
displaySet.othersFrameOfReferenceUID.includes(ReferencedFrameOfReferenceUID)));
|
|
2498
|
+
if (!referencedDisplaySet && otherDisplaySets.length >= 1) {
|
|
2499
|
+
console.warn('No display set with FrameOfReferenceUID', ReferencedFrameOfReferenceUID, 'single series, assuming data error, defaulting to only series.');
|
|
2500
|
+
return otherDisplaySets.find(displaySet => displaySet.Modality === 'SM');
|
|
2501
|
+
}
|
|
2502
|
+
return referencedDisplaySet;
|
|
2503
|
+
}
|
|
2504
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js
|
|
2505
|
+
|
|
2506
|
+
|
|
2507
|
+
|
|
2508
|
+
|
|
2509
|
+
|
|
2510
|
+
const {
|
|
2511
|
+
utils: DicomMicroscopySRSopClassHandler_utils
|
|
2512
|
+
} = core_src["default"];
|
|
2513
|
+
const DicomMicroscopySRSopClassHandler_SOP_CLASS_UIDS = {
|
|
2514
|
+
COMPREHENSIVE_3D_SR: '1.2.840.10008.5.1.4.1.1.88.34'
|
|
2515
|
+
};
|
|
2516
|
+
const DicomMicroscopySRSopClassHandler_SOPClassHandlerId = '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler';
|
|
2517
|
+
function _getReferencedFrameOfReferenceUID(naturalizedDataset) {
|
|
2518
|
+
const {
|
|
2519
|
+
ContentSequence
|
|
2520
|
+
} = naturalizedDataset;
|
|
2521
|
+
const imagingMeasurementsContentItem = ContentSequence.find(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.IMAGING_MEASUREMENTS);
|
|
2522
|
+
const firstMeasurementGroupContentItem = toArray(imagingMeasurementsContentItem.ContentSequence).find(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.MEASUREMENT_GROUP);
|
|
2523
|
+
const imageRegionContentItem = toArray(firstMeasurementGroupContentItem.ContentSequence).find(ci => ci.ConceptNameCodeSequence.CodeValue === dcmCodeValues.IMAGE_REGION);
|
|
2524
|
+
return imageRegionContentItem.ReferencedFrameOfReferenceUID;
|
|
2525
|
+
}
|
|
2526
|
+
function DicomMicroscopySRSopClassHandler_getDisplaySetsFromSeries(instances, servicesManager, extensionManager) {
|
|
2527
|
+
// If the series has no instances, stop here
|
|
2528
|
+
if (!instances || !instances.length) {
|
|
2529
|
+
throw new Error('No instances were provided');
|
|
2530
|
+
}
|
|
2531
|
+
const {
|
|
2532
|
+
displaySetService,
|
|
2533
|
+
microscopyService
|
|
2534
|
+
} = servicesManager.services;
|
|
2535
|
+
const instance = instances[0];
|
|
2536
|
+
|
|
2537
|
+
// TODO ! Consumption of DICOMMicroscopySRSOPClassHandler to a derived dataset or normal dataset?
|
|
2538
|
+
// TOOD -> Easy to swap this to a "non-derived" displaySet, but unfortunately need to put it in a different extension.
|
|
2539
|
+
const naturalizedDataset = core_src.DicomMetadataStore.getSeries(instance.StudyInstanceUID, instance.SeriesInstanceUID).instances[0];
|
|
2540
|
+
const ReferencedFrameOfReferenceUID = _getReferencedFrameOfReferenceUID(naturalizedDataset);
|
|
2541
|
+
const {
|
|
2542
|
+
FrameOfReferenceUID,
|
|
2543
|
+
SeriesDescription,
|
|
2544
|
+
ContentDate,
|
|
2545
|
+
ContentTime,
|
|
2546
|
+
SeriesNumber,
|
|
2547
|
+
StudyInstanceUID,
|
|
2548
|
+
SeriesInstanceUID,
|
|
2549
|
+
SOPInstanceUID,
|
|
2550
|
+
SOPClassUID
|
|
2551
|
+
} = instance;
|
|
2552
|
+
const displaySet = {
|
|
2553
|
+
plugin: 'microscopy',
|
|
2554
|
+
Modality: 'SR',
|
|
2555
|
+
altImageText: 'Microscopy SR',
|
|
2556
|
+
displaySetInstanceUID: DicomMicroscopySRSopClassHandler_utils.guid(),
|
|
2557
|
+
SOPInstanceUID,
|
|
2558
|
+
SeriesInstanceUID,
|
|
2559
|
+
StudyInstanceUID,
|
|
2560
|
+
ReferencedFrameOfReferenceUID,
|
|
2561
|
+
SOPClassHandlerId: DicomMicroscopySRSopClassHandler_SOPClassHandlerId,
|
|
2562
|
+
SOPClassUID,
|
|
2563
|
+
SeriesDescription,
|
|
2564
|
+
// Map the content date/time to the series date/time, these are only used for filtering.
|
|
2565
|
+
SeriesDate: ContentDate,
|
|
2566
|
+
SeriesTime: ContentTime,
|
|
2567
|
+
SeriesNumber,
|
|
2568
|
+
instance,
|
|
2569
|
+
metadata: naturalizedDataset,
|
|
2570
|
+
isDerived: true,
|
|
2571
|
+
isLoading: false,
|
|
2572
|
+
isLoaded: false,
|
|
2573
|
+
loadError: false
|
|
2574
|
+
};
|
|
2575
|
+
displaySet.load = function (referencedDisplaySet) {
|
|
2576
|
+
return loadSR(microscopyService, displaySet, referencedDisplaySet).catch(error => {
|
|
2577
|
+
displaySet.isLoaded = false;
|
|
2578
|
+
displaySet.loadError = true;
|
|
2579
|
+
throw new Error(error);
|
|
2580
|
+
});
|
|
2581
|
+
};
|
|
2582
|
+
displaySet.getSourceDisplaySet = function () {
|
|
2583
|
+
let allDisplaySets = [];
|
|
2584
|
+
const studyMetadata = core_src.DicomMetadataStore.getStudy(StudyInstanceUID);
|
|
2585
|
+
studyMetadata.series.forEach(series => {
|
|
2586
|
+
const displaySets = displaySetService.getDisplaySetsForSeries(series.SeriesInstanceUID);
|
|
2587
|
+
allDisplaySets = allDisplaySets.concat(displaySets);
|
|
2588
|
+
});
|
|
2589
|
+
return getSourceDisplaySet(allDisplaySets, displaySet);
|
|
2590
|
+
};
|
|
2591
|
+
return [displaySet];
|
|
2592
|
+
}
|
|
2593
|
+
function getDicomMicroscopySRSopClassHandler(_ref) {
|
|
2594
|
+
let {
|
|
2595
|
+
servicesManager,
|
|
2596
|
+
extensionManager
|
|
2597
|
+
} = _ref;
|
|
2598
|
+
const getDisplaySetsFromSeries = instances => {
|
|
2599
|
+
return DicomMicroscopySRSopClassHandler_getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
|
2600
|
+
};
|
|
2601
|
+
return {
|
|
2602
|
+
name: 'DicomMicroscopySRSopClassHandler',
|
|
2603
|
+
sopClassUids: [DicomMicroscopySRSopClassHandler_SOP_CLASS_UIDS.COMPREHENSIVE_3D_SR],
|
|
2604
|
+
getDisplaySetsFromSeries
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
;// CONCATENATED MODULE: ../../../extensions/dicom-microscopy/src/index.tsx
|
|
2608
|
+
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
2609
|
+
|
|
2610
|
+
|
|
2611
|
+
|
|
2612
|
+
|
|
2613
|
+
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
|
|
2617
|
+
const Component = /*#__PURE__*/react.lazy(() => {
|
|
2618
|
+
return Promise.all(/* import() */[__webpack_require__.e(780), __webpack_require__.e(331), __webpack_require__.e(935), __webpack_require__.e(151), __webpack_require__.e(664), __webpack_require__.e(82), __webpack_require__.e(55)]).then(__webpack_require__.bind(__webpack_require__, 76440));
|
|
2619
|
+
});
|
|
2620
|
+
const MicroscopyViewport = props => {
|
|
2621
|
+
return /*#__PURE__*/react.createElement(react.Suspense, {
|
|
2622
|
+
fallback: /*#__PURE__*/react.createElement("div", null, "Loading...")
|
|
2623
|
+
}, /*#__PURE__*/react.createElement(Component, props));
|
|
2624
|
+
};
|
|
2625
|
+
|
|
2626
|
+
/**
|
|
2627
|
+
* You can remove any of the following modules if you don't need them.
|
|
2628
|
+
*/
|
|
2629
|
+
/* harmony default export */ const dicom_microscopy_src = ({
|
|
2630
|
+
/**
|
|
2631
|
+
* Only required property. Should be a unique value across all extensions.
|
|
2632
|
+
* You ID can be anything you want, but it should be unique.
|
|
2633
|
+
*/
|
|
2634
|
+
id: id,
|
|
2635
|
+
async preRegistration(_ref) {
|
|
2636
|
+
let {
|
|
2637
|
+
servicesManager,
|
|
2638
|
+
commandsManager,
|
|
2639
|
+
configuration = {},
|
|
2640
|
+
appConfig
|
|
2641
|
+
} = _ref;
|
|
2642
|
+
servicesManager.registerService(MicroscopyService.REGISTRATION(servicesManager));
|
|
2643
|
+
},
|
|
2644
|
+
/**
|
|
2645
|
+
* ViewportModule should provide a list of viewports that will be available in OHIF
|
|
2646
|
+
* for Modes to consume and use in the viewports. Each viewport is defined by
|
|
2647
|
+
* {name, component} object. Example of a viewport module is the CornerstoneViewport
|
|
2648
|
+
* that is provided by the Cornerstone extension in OHIF.
|
|
2649
|
+
*/
|
|
2650
|
+
getViewportModule(_ref2) {
|
|
2651
|
+
let {
|
|
2652
|
+
servicesManager,
|
|
2653
|
+
extensionManager,
|
|
2654
|
+
commandsManager
|
|
2655
|
+
} = _ref2;
|
|
2656
|
+
/**
|
|
2657
|
+
*
|
|
2658
|
+
* @param props {*}
|
|
2659
|
+
* @param props.displaySets
|
|
2660
|
+
* @param props.viewportIndex
|
|
2661
|
+
* @param props.viewportLabel
|
|
2662
|
+
* @param props.dataSource
|
|
2663
|
+
* @param props.viewportOptions
|
|
2664
|
+
* @param props.displaySetOptions
|
|
2665
|
+
* @returns
|
|
2666
|
+
*/
|
|
2667
|
+
const ExtendedMicroscopyViewport = props => {
|
|
2668
|
+
const {
|
|
2669
|
+
viewportOptions
|
|
2670
|
+
} = props;
|
|
2671
|
+
const [viewportGrid, viewportGridService] = (0,src/* useViewportGrid */.O_)();
|
|
2672
|
+
const {
|
|
2673
|
+
viewports,
|
|
2674
|
+
activeViewportIndex
|
|
2675
|
+
} = viewportGrid;
|
|
2676
|
+
return /*#__PURE__*/react.createElement(MicroscopyViewport, _extends({
|
|
2677
|
+
servicesManager: servicesManager,
|
|
2678
|
+
extensionManager: extensionManager,
|
|
2679
|
+
commandsManager: commandsManager,
|
|
2680
|
+
activeViewportIndex: activeViewportIndex,
|
|
2681
|
+
setViewportActive: viewportIndex => {
|
|
2682
|
+
viewportGridService.setActiveViewportIndex(viewportIndex);
|
|
2683
|
+
},
|
|
2684
|
+
viewportData: viewportOptions
|
|
2685
|
+
}, props));
|
|
2686
|
+
};
|
|
2687
|
+
return [{
|
|
2688
|
+
name: 'microscopy-dicom',
|
|
2689
|
+
component: ExtendedMicroscopyViewport
|
|
2690
|
+
}];
|
|
2691
|
+
},
|
|
2692
|
+
/**
|
|
2693
|
+
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
|
2694
|
+
* available in OHIF for Modes to consume and use to create displaySets from Series.
|
|
2695
|
+
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
|
|
2696
|
+
* Examples include the default sop class handler provided by the default extension
|
|
2697
|
+
*/
|
|
2698
|
+
getSopClassHandlerModule(_ref3) {
|
|
2699
|
+
let {
|
|
2700
|
+
servicesManager,
|
|
2701
|
+
commandsManager,
|
|
2702
|
+
extensionManager
|
|
2703
|
+
} = _ref3;
|
|
2704
|
+
return [getDicomMicroscopySopClassHandler({
|
|
2705
|
+
servicesManager,
|
|
2706
|
+
extensionManager
|
|
2707
|
+
}), getDicomMicroscopySRSopClassHandler({
|
|
2708
|
+
servicesManager,
|
|
2709
|
+
extensionManager
|
|
2710
|
+
})];
|
|
2711
|
+
},
|
|
2712
|
+
getPanelModule: getPanelModule,
|
|
2713
|
+
getCommandsModule: getCommandsModule
|
|
2714
|
+
});
|
|
2715
|
+
|
|
2716
|
+
/***/ }),
|
|
2717
|
+
|
|
2718
|
+
/***/ 78753:
|
|
2719
|
+
/***/ (() => {
|
|
2720
|
+
|
|
2721
|
+
/* (ignored) */
|
|
2722
|
+
|
|
2723
|
+
/***/ })
|
|
2724
|
+
|
|
2725
|
+
}]);
|