@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,1842 @@
|
|
|
1
|
+
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[984],{
|
|
2
|
+
|
|
3
|
+
/***/ 65948:
|
|
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
|
+
"createReferencedImageDisplaySet": () => (/* reexport */ utils_createReferencedImageDisplaySet),
|
|
13
|
+
"default": () => (/* binding */ cornerstone_dicom_sr_src),
|
|
14
|
+
"hydrateStructuredReport": () => (/* reexport */ hydrateStructuredReport/* default */.Z),
|
|
15
|
+
"srProtocol": () => (/* reexport */ srProtocol)
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// EXTERNAL MODULE: ../../../node_modules/react/index.js
|
|
19
|
+
var react = __webpack_require__(32735);
|
|
20
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/package.json
|
|
21
|
+
const package_namespaceObject = JSON.parse('{"u2":"@ohif/extension-cornerstone-dicom-sr"}');
|
|
22
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/id.js
|
|
23
|
+
|
|
24
|
+
const id = package_namespaceObject.u2;
|
|
25
|
+
const SOPClassHandlerName = 'dicom-sr';
|
|
26
|
+
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
|
27
|
+
|
|
28
|
+
// EXTERNAL MODULE: ../../core/src/index.ts + 101 modules
|
|
29
|
+
var src = __webpack_require__(48501);
|
|
30
|
+
// EXTERNAL MODULE: ../../../node_modules/gl-matrix/esm/index.js + 10 modules
|
|
31
|
+
var esm = __webpack_require__(88256);
|
|
32
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js + 321 modules
|
|
33
|
+
var dist_esm = __webpack_require__(57270);
|
|
34
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 335 modules
|
|
35
|
+
var core_dist_esm = __webpack_require__(77331);
|
|
36
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js
|
|
37
|
+
var dicomSRModule = __webpack_require__(94709);
|
|
38
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/constants/scoordTypes.js
|
|
39
|
+
/* harmony default export */ const scoordTypes = ({
|
|
40
|
+
POINT: 'POINT',
|
|
41
|
+
MULTIPOINT: 'MULTIPOINT',
|
|
42
|
+
POLYLINE: 'POLYLINE',
|
|
43
|
+
CIRCLE: 'CIRCLE',
|
|
44
|
+
ELLIPSE: 'ELLIPSE'
|
|
45
|
+
});
|
|
46
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class DICOMSRDisplayTool extends dist_esm.AnnotationTool {
|
|
52
|
+
constructor() {
|
|
53
|
+
let toolProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
54
|
+
let defaultToolProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
|
|
55
|
+
configuration: {}
|
|
56
|
+
};
|
|
57
|
+
super(toolProps, defaultToolProps);
|
|
58
|
+
// This tool should not inherit from AnnotationTool and we should not need
|
|
59
|
+
// to add the following lines.
|
|
60
|
+
this.isPointNearTool = () => null;
|
|
61
|
+
this.getHandleNearImagePoint = () => null;
|
|
62
|
+
this.renderAnnotation = (enabledElement, svgDrawingHelper) => {
|
|
63
|
+
const {
|
|
64
|
+
viewport
|
|
65
|
+
} = enabledElement;
|
|
66
|
+
const {
|
|
67
|
+
element
|
|
68
|
+
} = viewport;
|
|
69
|
+
let annotations = dist_esm.annotation.state.getAnnotations(this.getToolName(), element);
|
|
70
|
+
|
|
71
|
+
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
|
72
|
+
if (!annotations?.length) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
annotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
|
76
|
+
if (!annotations?.length) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const trackingUniqueIdentifiersForElement = (0,dicomSRModule/* getTrackingUniqueIdentifiersForElement */.yR)(element);
|
|
80
|
+
const {
|
|
81
|
+
activeIndex,
|
|
82
|
+
trackingUniqueIdentifiers
|
|
83
|
+
} = trackingUniqueIdentifiersForElement;
|
|
84
|
+
const activeTrackingUniqueIdentifier = trackingUniqueIdentifiers[activeIndex];
|
|
85
|
+
|
|
86
|
+
// Filter toolData to only render the data for the active SR.
|
|
87
|
+
const filteredAnnotations = annotations.filter(annotation => trackingUniqueIdentifiers.includes(annotation.data?.cachedStats?.TrackingUniqueIdentifier));
|
|
88
|
+
if (!viewport._actors?.size) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const styleSpecifier = {
|
|
92
|
+
toolGroupId: this.toolGroupId,
|
|
93
|
+
toolName: this.getToolName(),
|
|
94
|
+
viewportId: enabledElement.viewport.id
|
|
95
|
+
};
|
|
96
|
+
for (let i = 0; i < filteredAnnotations.length; i++) {
|
|
97
|
+
const annotation = filteredAnnotations[i];
|
|
98
|
+
const annotationUID = annotation.annotationUID;
|
|
99
|
+
const {
|
|
100
|
+
renderableData
|
|
101
|
+
} = annotation.data.cachedStats;
|
|
102
|
+
const {
|
|
103
|
+
cachedStats
|
|
104
|
+
} = annotation.data;
|
|
105
|
+
const {
|
|
106
|
+
referencedImageId
|
|
107
|
+
} = annotation.metadata;
|
|
108
|
+
styleSpecifier.annotationUID = annotationUID;
|
|
109
|
+
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
|
110
|
+
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
|
111
|
+
const color = cachedStats.TrackingUniqueIdentifier === activeTrackingUniqueIdentifier ? 'rgb(0, 255, 0)' : this.getStyle('color', styleSpecifier, annotation);
|
|
112
|
+
const options = {
|
|
113
|
+
color,
|
|
114
|
+
lineDash,
|
|
115
|
+
lineWidth
|
|
116
|
+
};
|
|
117
|
+
Object.keys(renderableData).forEach(GraphicType => {
|
|
118
|
+
const renderableDataForGraphicType = renderableData[GraphicType];
|
|
119
|
+
let renderMethod;
|
|
120
|
+
let canvasCoordinatesAdapter;
|
|
121
|
+
switch (GraphicType) {
|
|
122
|
+
case scoordTypes.POINT:
|
|
123
|
+
renderMethod = this.renderPoint;
|
|
124
|
+
break;
|
|
125
|
+
case scoordTypes.MULTIPOINT:
|
|
126
|
+
renderMethod = this.renderMultipoint;
|
|
127
|
+
break;
|
|
128
|
+
case scoordTypes.POLYLINE:
|
|
129
|
+
renderMethod = this.renderPolyLine;
|
|
130
|
+
break;
|
|
131
|
+
case scoordTypes.CIRCLE:
|
|
132
|
+
renderMethod = this.renderEllipse;
|
|
133
|
+
break;
|
|
134
|
+
case scoordTypes.ELLIPSE:
|
|
135
|
+
renderMethod = this.renderEllipse;
|
|
136
|
+
canvasCoordinatesAdapter = dist_esm.utilities.math.ellipse.getCanvasEllipseCorners;
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
throw new Error(`Unsupported GraphicType: ${GraphicType}`);
|
|
140
|
+
}
|
|
141
|
+
const canvasCoordinates = renderMethod(svgDrawingHelper, viewport, renderableDataForGraphicType, annotationUID, referencedImageId, options);
|
|
142
|
+
this.renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, canvasCoordinatesAdapter, annotation, styleSpecifier, options);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
_getTextBoxLinesFromLabels(labels) {
|
|
148
|
+
// TODO -> max 3 for now (label + shortAxis + longAxis), need a generic solution for this!
|
|
149
|
+
|
|
150
|
+
const labelLength = Math.min(labels.length, 3);
|
|
151
|
+
const lines = [];
|
|
152
|
+
for (let i = 0; i < labelLength; i++) {
|
|
153
|
+
const labelEntry = labels[i];
|
|
154
|
+
lines.push(`${_labelToShorthand(labelEntry.label)}${labelEntry.value}`);
|
|
155
|
+
}
|
|
156
|
+
return lines;
|
|
157
|
+
}
|
|
158
|
+
renderPolyLine(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
159
|
+
const drawingOptions = {
|
|
160
|
+
color: options.color,
|
|
161
|
+
width: options.lineWidth
|
|
162
|
+
};
|
|
163
|
+
let allCanvasCoordinates = [];
|
|
164
|
+
renderableData.map((data, index) => {
|
|
165
|
+
const canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
|
166
|
+
const lineUID = `${index}`;
|
|
167
|
+
if (canvasCoordinates.length === 2) {
|
|
168
|
+
dist_esm.drawing.drawLine(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates[0], canvasCoordinates[1], drawingOptions);
|
|
169
|
+
} else {
|
|
170
|
+
dist_esm.drawing.drawPolyline(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates, drawingOptions);
|
|
171
|
+
}
|
|
172
|
+
allCanvasCoordinates = allCanvasCoordinates.concat(canvasCoordinates);
|
|
173
|
+
});
|
|
174
|
+
return allCanvasCoordinates; // used for drawing textBox
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
renderMultipoint(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
178
|
+
let canvasCoordinates;
|
|
179
|
+
renderableData.map((data, index) => {
|
|
180
|
+
canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
|
181
|
+
const handleGroupUID = '0';
|
|
182
|
+
dist_esm.drawing.drawHandles(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, {
|
|
183
|
+
color: options.color
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
renderPoint(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
188
|
+
const canvasCoordinates = [];
|
|
189
|
+
renderableData.map((data, index) => {
|
|
190
|
+
const point = data[0];
|
|
191
|
+
// This gives us one point for arrow
|
|
192
|
+
canvasCoordinates.push(viewport.worldToCanvas(point));
|
|
193
|
+
|
|
194
|
+
// We get the other point for the arrow by using the image size
|
|
195
|
+
const imagePixelModule = core_dist_esm.metaData.get('imagePixelModule', referencedImageId);
|
|
196
|
+
let xOffset = 10;
|
|
197
|
+
let yOffset = 10;
|
|
198
|
+
if (imagePixelModule) {
|
|
199
|
+
const {
|
|
200
|
+
columns,
|
|
201
|
+
rows
|
|
202
|
+
} = imagePixelModule;
|
|
203
|
+
xOffset = columns / 10;
|
|
204
|
+
yOffset = rows / 10;
|
|
205
|
+
}
|
|
206
|
+
const imagePoint = core_dist_esm.utilities.worldToImageCoords(referencedImageId, point);
|
|
207
|
+
const arrowEnd = core_dist_esm.utilities.imageToWorldCoords(referencedImageId, [imagePoint[0] + xOffset, imagePoint[1] + yOffset]);
|
|
208
|
+
canvasCoordinates.push(viewport.worldToCanvas(arrowEnd));
|
|
209
|
+
const arrowUID = `${index}`;
|
|
210
|
+
|
|
211
|
+
// Todo: handle drawing probe as probe, currently we are drawing it as an arrow
|
|
212
|
+
dist_esm.drawing.drawArrow(svgDrawingHelper, annotationUID, arrowUID, canvasCoordinates[1], canvasCoordinates[0], {
|
|
213
|
+
color: options.color,
|
|
214
|
+
width: options.lineWidth
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
return canvasCoordinates; // used for drawing textBox
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
renderEllipse(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
221
|
+
let canvasCoordinates;
|
|
222
|
+
renderableData.map((data, index) => {
|
|
223
|
+
if (data.length === 0) {
|
|
224
|
+
// since oblique ellipse is not supported for hydration right now
|
|
225
|
+
// we just return
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const ellipsePointsWorld = data;
|
|
229
|
+
const rotation = viewport.getRotation();
|
|
230
|
+
canvasCoordinates = ellipsePointsWorld.map(p => viewport.worldToCanvas(p));
|
|
231
|
+
let canvasCorners;
|
|
232
|
+
if (rotation == 90 || rotation == 270) {
|
|
233
|
+
canvasCorners = dist_esm.utilities.math.ellipse.getCanvasEllipseCorners([canvasCoordinates[2], canvasCoordinates[3], canvasCoordinates[0], canvasCoordinates[1]]);
|
|
234
|
+
} else {
|
|
235
|
+
canvasCorners = dist_esm.utilities.math.ellipse.getCanvasEllipseCorners(canvasCoordinates);
|
|
236
|
+
}
|
|
237
|
+
const lineUID = `${index}`;
|
|
238
|
+
dist_esm.drawing.drawEllipse(svgDrawingHelper, annotationUID, lineUID, canvasCorners[0], canvasCorners[1], {
|
|
239
|
+
color: options.color,
|
|
240
|
+
width: options.lineWidth
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
return canvasCoordinates;
|
|
244
|
+
}
|
|
245
|
+
renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, canvasCoordinatesAdapter, annotation, styleSpecifier) {
|
|
246
|
+
let options = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {};
|
|
247
|
+
if (!canvasCoordinates || !annotation) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const {
|
|
251
|
+
annotationUID,
|
|
252
|
+
data = {}
|
|
253
|
+
} = annotation;
|
|
254
|
+
const {
|
|
255
|
+
label
|
|
256
|
+
} = data;
|
|
257
|
+
const {
|
|
258
|
+
color
|
|
259
|
+
} = options;
|
|
260
|
+
let adaptedCanvasCoordinates = canvasCoordinates;
|
|
261
|
+
// adapt coordinates if there is an adapter
|
|
262
|
+
if (typeof canvasCoordinatesAdapter === 'function') {
|
|
263
|
+
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
|
264
|
+
}
|
|
265
|
+
const textLines = this._getTextBoxLinesFromLabels(label);
|
|
266
|
+
const canvasTextBoxCoords = dist_esm.utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
|
267
|
+
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
|
268
|
+
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
|
269
|
+
const textBoxUID = '1';
|
|
270
|
+
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
|
271
|
+
const boundingBox = dist_esm.drawing.drawLinkedTextBox(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, canvasCoordinates, {}, {
|
|
272
|
+
...textBoxOptions,
|
|
273
|
+
color
|
|
274
|
+
});
|
|
275
|
+
const {
|
|
276
|
+
x: left,
|
|
277
|
+
y: top,
|
|
278
|
+
width,
|
|
279
|
+
height
|
|
280
|
+
} = boundingBox;
|
|
281
|
+
annotation.data.handles.textBox.worldBoundingBox = {
|
|
282
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
283
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
284
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
285
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height])
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
DICOMSRDisplayTool.toolName = 'DICOMSRDisplay';
|
|
290
|
+
const SHORT_HAND_MAP = {
|
|
291
|
+
'Short Axis': 'W: ',
|
|
292
|
+
'Long Axis': 'L: ',
|
|
293
|
+
AREA: 'Area: ',
|
|
294
|
+
Length: '',
|
|
295
|
+
CORNERSTONEFREETEXT: ''
|
|
296
|
+
};
|
|
297
|
+
function _labelToShorthand(label) {
|
|
298
|
+
const shortHand = SHORT_HAND_MAP[label];
|
|
299
|
+
if (shortHand !== undefined) {
|
|
300
|
+
return shortHand;
|
|
301
|
+
}
|
|
302
|
+
return label;
|
|
303
|
+
}
|
|
304
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/toolNames.ts
|
|
305
|
+
|
|
306
|
+
const toolNames = {
|
|
307
|
+
DICOMSRDisplay: DICOMSRDisplayTool.toolName,
|
|
308
|
+
SRLength: 'SRLength',
|
|
309
|
+
SRBidirectional: 'SRBidirectional',
|
|
310
|
+
SREllipticalROI: 'SREllipticalROI',
|
|
311
|
+
SRCircleROI: 'SRCircleROI',
|
|
312
|
+
SRArrowAnnotate: 'SRArrowAnnotate',
|
|
313
|
+
SRAngle: 'SRAngle',
|
|
314
|
+
SRCobbAngle: 'SRCobbAngle',
|
|
315
|
+
SRRectangleROI: 'SRRectangleROI',
|
|
316
|
+
SRPlanarFreehandROI: 'SRPlanarFreehandROI'
|
|
317
|
+
};
|
|
318
|
+
/* harmony default export */ const tools_toolNames = (toolNames);
|
|
319
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
const EPSILON = 1e-4;
|
|
326
|
+
const supportedLegacyCornerstoneTags = (/* unused pure expression or super */ null && (['cornerstoneTools@^4.0.0']));
|
|
327
|
+
function addMeasurement(measurement, imageId, displaySetInstanceUID) {
|
|
328
|
+
// TODO -> Render rotated ellipse .
|
|
329
|
+
const toolName = tools_toolNames.DICOMSRDisplay;
|
|
330
|
+
const measurementData = {
|
|
331
|
+
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
|
332
|
+
renderableData: {},
|
|
333
|
+
labels: measurement.labels,
|
|
334
|
+
imageId
|
|
335
|
+
};
|
|
336
|
+
measurement.coords.forEach(coord => {
|
|
337
|
+
const {
|
|
338
|
+
GraphicType,
|
|
339
|
+
GraphicData
|
|
340
|
+
} = coord;
|
|
341
|
+
if (measurementData.renderableData[GraphicType] === undefined) {
|
|
342
|
+
measurementData.renderableData[GraphicType] = [];
|
|
343
|
+
}
|
|
344
|
+
measurementData.renderableData[GraphicType].push(_getRenderableData(GraphicType, GraphicData, imageId, measurement.TrackingIdentifier));
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Use the metadata provider to grab its imagePlaneModule metadata
|
|
348
|
+
const imagePlaneModule = core_dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
349
|
+
const annotationManager = dist_esm.annotation.state.getAnnotationManager();
|
|
350
|
+
|
|
351
|
+
// Create Cornerstone3D Annotation from measurement
|
|
352
|
+
const frameNumber = measurement.coords[0].ReferencedSOPSequence && measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber || 1;
|
|
353
|
+
const SRAnnotation = {
|
|
354
|
+
annotationUID: measurement.TrackingUniqueIdentifier,
|
|
355
|
+
metadata: {
|
|
356
|
+
FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID,
|
|
357
|
+
toolName: toolName,
|
|
358
|
+
referencedImageId: imageId
|
|
359
|
+
},
|
|
360
|
+
data: {
|
|
361
|
+
label: measurement.labels,
|
|
362
|
+
handles: {
|
|
363
|
+
textBox: {}
|
|
364
|
+
},
|
|
365
|
+
cachedStats: {
|
|
366
|
+
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
|
367
|
+
renderableData: measurementData.renderableData
|
|
368
|
+
},
|
|
369
|
+
frameNumber: frameNumber
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
annotationManager.addAnnotation(SRAnnotation);
|
|
373
|
+
measurement.loaded = true;
|
|
374
|
+
measurement.imageId = imageId;
|
|
375
|
+
measurement.displaySetInstanceUID = displaySetInstanceUID;
|
|
376
|
+
|
|
377
|
+
// Remove the unneeded coord now its processed, but keep the SOPInstanceUID.
|
|
378
|
+
// NOTE: We assume that each SCOORD in the MeasurementGroup maps onto one frame,
|
|
379
|
+
// It'd be super weird if it didn't anyway as a SCOORD.
|
|
380
|
+
measurement.ReferencedSOPInstanceUID = measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
|
381
|
+
measurement.frameNumber = frameNumber;
|
|
382
|
+
delete measurement.coords;
|
|
383
|
+
}
|
|
384
|
+
function _getRenderableData(GraphicType, GraphicData, imageId, TrackingIdentifier) {
|
|
385
|
+
const [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
|
|
386
|
+
let renderableData;
|
|
387
|
+
switch (GraphicType) {
|
|
388
|
+
case scoordTypes.POINT:
|
|
389
|
+
case scoordTypes.MULTIPOINT:
|
|
390
|
+
case scoordTypes.POLYLINE:
|
|
391
|
+
renderableData = [];
|
|
392
|
+
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
393
|
+
const worldPos = core_dist_esm.utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
|
394
|
+
renderableData.push(worldPos);
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
397
|
+
case scoordTypes.CIRCLE:
|
|
398
|
+
{
|
|
399
|
+
const pointsWorld = [];
|
|
400
|
+
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
401
|
+
const worldPos = core_dist_esm.utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
|
402
|
+
pointsWorld.push(worldPos);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// We do not have an explicit draw circle svg helper in Cornerstone3D at
|
|
406
|
+
// this time, but we can use the ellipse svg helper to draw a circle, so
|
|
407
|
+
// here we reshape the data for that purpose.
|
|
408
|
+
const center = pointsWorld[0];
|
|
409
|
+
const onPerimeter = pointsWorld[1];
|
|
410
|
+
const radius = esm/* vec3.distance */.R3.distance(center, onPerimeter);
|
|
411
|
+
const imagePlaneModule = core_dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
412
|
+
if (!imagePlaneModule) {
|
|
413
|
+
throw new Error('No imagePlaneModule found');
|
|
414
|
+
}
|
|
415
|
+
const {
|
|
416
|
+
columnCosines,
|
|
417
|
+
rowCosines
|
|
418
|
+
} = imagePlaneModule;
|
|
419
|
+
|
|
420
|
+
// we need to get major/minor axis (which are both the same size major = minor)
|
|
421
|
+
|
|
422
|
+
// first axisStart
|
|
423
|
+
const firstAxisStart = esm/* vec3.create */.R3.create();
|
|
424
|
+
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
|
|
425
|
+
const firstAxisEnd = esm/* vec3.create */.R3.create();
|
|
426
|
+
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
|
|
427
|
+
|
|
428
|
+
// second axisStart
|
|
429
|
+
const secondAxisStart = esm/* vec3.create */.R3.create();
|
|
430
|
+
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
|
|
431
|
+
const secondAxisEnd = esm/* vec3.create */.R3.create();
|
|
432
|
+
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(secondAxisEnd, center, rowCosines, -radius);
|
|
433
|
+
renderableData = [firstAxisStart, firstAxisEnd, secondAxisStart, secondAxisEnd];
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case scoordTypes.ELLIPSE:
|
|
437
|
+
{
|
|
438
|
+
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
|
439
|
+
// But Cornerstone3D points are ordered as top, bottom, left, right for the
|
|
440
|
+
// ellipse so we need to identify if the majorAxis is horizontal or vertical
|
|
441
|
+
// and then choose the correct points to use for the ellipse.
|
|
442
|
+
|
|
443
|
+
const pointsWorld = [];
|
|
444
|
+
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
445
|
+
const worldPos = core_dist_esm.utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
|
446
|
+
pointsWorld.push(worldPos);
|
|
447
|
+
}
|
|
448
|
+
const majorAxisStart = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[0]);
|
|
449
|
+
const majorAxisEnd = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[1]);
|
|
450
|
+
const minorAxisStart = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[2]);
|
|
451
|
+
const minorAxisEnd = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[3]);
|
|
452
|
+
const majorAxisVec = esm/* vec3.create */.R3.create();
|
|
453
|
+
esm/* vec3.sub */.R3.sub(majorAxisVec, majorAxisEnd, majorAxisStart);
|
|
454
|
+
|
|
455
|
+
// normalize majorAxisVec to avoid scaling issues
|
|
456
|
+
esm/* vec3.normalize */.R3.normalize(majorAxisVec, majorAxisVec);
|
|
457
|
+
const minorAxisVec = esm/* vec3.create */.R3.create();
|
|
458
|
+
esm/* vec3.sub */.R3.sub(minorAxisVec, minorAxisEnd, minorAxisStart);
|
|
459
|
+
esm/* vec3.normalize */.R3.normalize(minorAxisVec, minorAxisVec);
|
|
460
|
+
const imagePlaneModule = core_dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
461
|
+
if (!imagePlaneModule) {
|
|
462
|
+
throw new Error('imageId does not have imagePlaneModule metadata');
|
|
463
|
+
}
|
|
464
|
+
const {
|
|
465
|
+
columnCosines
|
|
466
|
+
} = imagePlaneModule;
|
|
467
|
+
|
|
468
|
+
// find which axis is parallel to the columnCosines
|
|
469
|
+
const columnCosinesVec = esm/* vec3.fromValues */.R3.fromValues(...columnCosines);
|
|
470
|
+
const projectedMajorAxisOnColVec = Math.abs(esm/* vec3.dot */.R3.dot(columnCosinesVec, majorAxisVec));
|
|
471
|
+
const projectedMinorAxisOnColVec = Math.abs(esm/* vec3.dot */.R3.dot(columnCosinesVec, minorAxisVec));
|
|
472
|
+
const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec);
|
|
473
|
+
const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec);
|
|
474
|
+
renderableData = [];
|
|
475
|
+
if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) {
|
|
476
|
+
renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]];
|
|
477
|
+
} else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) {
|
|
478
|
+
renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]];
|
|
479
|
+
} else {
|
|
480
|
+
console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED');
|
|
481
|
+
}
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
default:
|
|
485
|
+
console.warn('Unsupported GraphicType:', GraphicType);
|
|
486
|
+
}
|
|
487
|
+
return renderableData;
|
|
488
|
+
}
|
|
489
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/@cornerstonejs/adapters.es.js
|
|
490
|
+
var adapters_es = __webpack_require__(4606);
|
|
491
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js
|
|
492
|
+
|
|
493
|
+
const cornerstoneAdapters = adapters_es.adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
|
494
|
+
const isRehydratable_supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
|
495
|
+
const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Checks if the given `displaySet`can be rehydrated into the `measurementService`.
|
|
499
|
+
*
|
|
500
|
+
* @param {object} displaySet The SR `displaySet` to check.
|
|
501
|
+
* @param {object[]} mappings The CornerstoneTools 4 mappings to the `measurementService`.
|
|
502
|
+
* @returns {boolean} True if the SR can be rehydrated into the `measurementService`.
|
|
503
|
+
*/
|
|
504
|
+
function isRehydratable(displaySet, mappings) {
|
|
505
|
+
if (!mappings || !mappings.length) {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
const mappingDefinitions = mappings.map(m => m.annotationType);
|
|
509
|
+
const {
|
|
510
|
+
measurements
|
|
511
|
+
} = displaySet;
|
|
512
|
+
const adapterKeys = Object.keys(cornerstoneAdapters).filter(adapterKey => typeof cornerstoneAdapters[adapterKey].isValidCornerstoneTrackingIdentifier === 'function');
|
|
513
|
+
const adapters = [];
|
|
514
|
+
adapterKeys.forEach(key => {
|
|
515
|
+
if (mappingDefinitions.includes(key)) {
|
|
516
|
+
// Must have both a dcmjs adapter and a measurementService
|
|
517
|
+
// Definition in order to be a candidate for import.
|
|
518
|
+
adapters.push(cornerstoneAdapters[key]);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
for (let i = 0; i < measurements.length; i++) {
|
|
522
|
+
const {
|
|
523
|
+
TrackingIdentifier
|
|
524
|
+
} = measurements[i] || {};
|
|
525
|
+
const hydratable = adapters.some(adapter => {
|
|
526
|
+
let [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
|
|
527
|
+
if (isRehydratable_supportedLegacyCornerstoneTags.includes(cornerstoneTag)) {
|
|
528
|
+
cornerstoneTag = CORNERSTONE_3D_TAG;
|
|
529
|
+
}
|
|
530
|
+
const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`;
|
|
531
|
+
return adapter.isValidCornerstoneTrackingIdentifier(mappedTrackingIdentifier);
|
|
532
|
+
});
|
|
533
|
+
if (hydratable) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
console.log('Measurement is not rehydratable', TrackingIdentifier, measurements[i]);
|
|
537
|
+
}
|
|
538
|
+
console.log('No measurements found which were rehydratable');
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
const {
|
|
548
|
+
CodeScheme: Cornerstone3DCodeScheme
|
|
549
|
+
} = adapters_es.adaptersSR.Cornerstone3D;
|
|
550
|
+
const {
|
|
551
|
+
ImageSet,
|
|
552
|
+
MetadataProvider: metadataProvider
|
|
553
|
+
} = src.classes;
|
|
554
|
+
|
|
555
|
+
// TODO ->
|
|
556
|
+
// Add SR thumbnail
|
|
557
|
+
// Make viewport
|
|
558
|
+
// Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport.
|
|
559
|
+
|
|
560
|
+
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.88.11',
|
|
561
|
+
//BASIC_TEXT_SR:
|
|
562
|
+
'1.2.840.10008.5.1.4.1.1.88.22',
|
|
563
|
+
//ENHANCED_SR:
|
|
564
|
+
'1.2.840.10008.5.1.4.1.1.88.33',
|
|
565
|
+
//COMPREHENSIVE_SR:
|
|
566
|
+
'1.2.840.10008.5.1.4.1.1.88.34' //COMPREHENSIVE_3D_SR:
|
|
567
|
+
];
|
|
568
|
+
|
|
569
|
+
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
|
570
|
+
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|
571
|
+
const validateSameStudyUID = (uid, instances) => {
|
|
572
|
+
instances.forEach(it => {
|
|
573
|
+
if (it.StudyInstanceUID !== uid) {
|
|
574
|
+
console.warn('Not all instances have the same UID', uid, it);
|
|
575
|
+
throw new Error(`Instances ${it.SOPInstanceUID} does not belong to ${uid}`);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
};
|
|
579
|
+
const CodeNameCodeSequenceValues = {
|
|
580
|
+
ImagingMeasurementReport: '126000',
|
|
581
|
+
ImageLibrary: '111028',
|
|
582
|
+
ImagingMeasurements: '126010',
|
|
583
|
+
MeasurementGroup: '125007',
|
|
584
|
+
ImageLibraryGroup: '126200',
|
|
585
|
+
TrackingUniqueIdentifier: '112040',
|
|
586
|
+
TrackingIdentifier: '112039',
|
|
587
|
+
Finding: '121071',
|
|
588
|
+
FindingSite: 'G-C0E3',
|
|
589
|
+
// SRT
|
|
590
|
+
CornerstoneFreeText: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT //
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const CodingSchemeDesignators = {
|
|
594
|
+
SRT: 'SRT',
|
|
595
|
+
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4']
|
|
596
|
+
};
|
|
597
|
+
const RELATIONSHIP_TYPE = {
|
|
598
|
+
INFERRED_FROM: 'INFERRED FROM',
|
|
599
|
+
CONTAINS: 'CONTAINS'
|
|
600
|
+
};
|
|
601
|
+
const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Adds instances to the DICOM SR series, rather than creating a new
|
|
605
|
+
* series, so that as SR's are saved, they append to the series, and the
|
|
606
|
+
* key image display set gets updated as well, containing just the new series.
|
|
607
|
+
* @param instances is a list of instances from THIS series that are not
|
|
608
|
+
* in this DICOM SR Display Set already.
|
|
609
|
+
*/
|
|
610
|
+
function addInstances(instances, displaySetService) {
|
|
611
|
+
this.instances.push(...instances);
|
|
612
|
+
src.utils.sortStudyInstances(this.instances);
|
|
613
|
+
// The last instance is the newest one, so is the one most interesting.
|
|
614
|
+
// Eventually, the SR viewer should have the ability to choose which SR
|
|
615
|
+
// gets loaded, and to navigate among them.
|
|
616
|
+
this.instance = this.instances[this.instances.length - 1];
|
|
617
|
+
this.isLoaded = false;
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* DICOM SR SOP Class Handler
|
|
623
|
+
* For all referenced images in the TID 1500/300 sections, add an image to the
|
|
624
|
+
* display.
|
|
625
|
+
* @param instances is a set of instances all from the same series
|
|
626
|
+
* @param servicesManager is the services that can be used for creating
|
|
627
|
+
* @returns The list of display sets created for the given instances object
|
|
628
|
+
*/
|
|
629
|
+
function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) {
|
|
630
|
+
// If the series has no instances, stop here
|
|
631
|
+
if (!instances || !instances.length) {
|
|
632
|
+
throw new Error('No instances were provided');
|
|
633
|
+
}
|
|
634
|
+
src.utils.sortStudyInstances(instances);
|
|
635
|
+
// The last instance is the newest one, so is the one most interesting.
|
|
636
|
+
// Eventually, the SR viewer should have the ability to choose which SR
|
|
637
|
+
// gets loaded, and to navigate among them.
|
|
638
|
+
const instance = instances[instances.length - 1];
|
|
639
|
+
const {
|
|
640
|
+
StudyInstanceUID,
|
|
641
|
+
SeriesInstanceUID,
|
|
642
|
+
SOPInstanceUID,
|
|
643
|
+
SeriesDescription,
|
|
644
|
+
SeriesNumber,
|
|
645
|
+
SeriesDate,
|
|
646
|
+
ConceptNameCodeSequence,
|
|
647
|
+
SOPClassUID
|
|
648
|
+
} = instance;
|
|
649
|
+
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
|
650
|
+
if (!ConceptNameCodeSequence || ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.ImagingMeasurementReport) {
|
|
651
|
+
servicesManager.services.uiNotificationService.show({
|
|
652
|
+
title: 'DICOM SR',
|
|
653
|
+
message: 'OHIF only supports TID1500 Imaging Measurement Report Structured Reports. The SR you’re trying to view is not supported.',
|
|
654
|
+
type: 'warning',
|
|
655
|
+
duration: 6000
|
|
656
|
+
});
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
const displaySet = {
|
|
660
|
+
//plugin: id,
|
|
661
|
+
Modality: 'SR',
|
|
662
|
+
displaySetInstanceUID: src.utils.guid(),
|
|
663
|
+
SeriesDescription,
|
|
664
|
+
SeriesNumber,
|
|
665
|
+
SeriesDate,
|
|
666
|
+
SOPInstanceUID,
|
|
667
|
+
SeriesInstanceUID,
|
|
668
|
+
StudyInstanceUID,
|
|
669
|
+
SOPClassHandlerId: SOPClassHandlerId,
|
|
670
|
+
SOPClassUID,
|
|
671
|
+
instances,
|
|
672
|
+
referencedImages: null,
|
|
673
|
+
measurements: null,
|
|
674
|
+
isDerivedDisplaySet: true,
|
|
675
|
+
isLoaded: false,
|
|
676
|
+
sopClassUids,
|
|
677
|
+
instance,
|
|
678
|
+
addInstances
|
|
679
|
+
};
|
|
680
|
+
displaySet.load = () => _load(displaySet, servicesManager, extensionManager);
|
|
681
|
+
return [displaySet];
|
|
682
|
+
}
|
|
683
|
+
function _load(displaySet, servicesManager, extensionManager) {
|
|
684
|
+
const {
|
|
685
|
+
displaySetService,
|
|
686
|
+
measurementService
|
|
687
|
+
} = servicesManager.services;
|
|
688
|
+
const dataSources = extensionManager.getDataSources();
|
|
689
|
+
const dataSource = dataSources[0];
|
|
690
|
+
const {
|
|
691
|
+
ContentSequence
|
|
692
|
+
} = displaySet.instance;
|
|
693
|
+
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
|
694
|
+
displaySet.measurements = _getMeasurements(ContentSequence);
|
|
695
|
+
const mappings = measurementService.getSourceMappings(CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION);
|
|
696
|
+
displaySet.isHydrated = false;
|
|
697
|
+
displaySet.isRehydratable = isRehydratable(displaySet, mappings);
|
|
698
|
+
displaySet.isLoaded = true;
|
|
699
|
+
|
|
700
|
+
// Check currently added displaySets and add measurements if the sources exist.
|
|
701
|
+
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
|
702
|
+
_checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// Subscribe to new displaySets as the source may come in after.
|
|
706
|
+
displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => {
|
|
707
|
+
const {
|
|
708
|
+
displaySetsAdded
|
|
709
|
+
} = data;
|
|
710
|
+
// If there are still some measurements that have not yet been loaded into cornerstone,
|
|
711
|
+
// See if we can load them onto any of the new displaySets.
|
|
712
|
+
displaySetsAdded.forEach(newDisplaySet => {
|
|
713
|
+
_checkIfCanAddMeasurementsToDisplaySet(displaySet, newDisplaySet, dataSource);
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource) {
|
|
718
|
+
let unloadedMeasurements = srDisplaySet.measurements.filter(measurement => measurement.loaded === false);
|
|
719
|
+
if (unloadedMeasurements.length === 0) {
|
|
720
|
+
// All already loaded!
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (!newDisplaySet instanceof ImageSet) {
|
|
724
|
+
// This also filters out _this_ displaySet, as it is not an ImageSet.
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const {
|
|
728
|
+
sopClassUids,
|
|
729
|
+
images
|
|
730
|
+
} = newDisplaySet;
|
|
731
|
+
|
|
732
|
+
// Check if any have the newDisplaySet is the correct SOPClass.
|
|
733
|
+
unloadedMeasurements = unloadedMeasurements.filter(measurement => measurement.coords.some(coord => sopClassUids.includes(coord.ReferencedSOPSequence.ReferencedSOPClassUID)));
|
|
734
|
+
if (unloadedMeasurements.length === 0) {
|
|
735
|
+
// New displaySet isn't the correct SOPClass, so can't contain the referenced images.
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const SOPInstanceUIDs = [];
|
|
739
|
+
unloadedMeasurements.forEach(measurement => {
|
|
740
|
+
const {
|
|
741
|
+
coords
|
|
742
|
+
} = measurement;
|
|
743
|
+
coords.forEach(coord => {
|
|
744
|
+
const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
|
745
|
+
if (!SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
|
746
|
+
SOPInstanceUIDs.push(SOPInstanceUID);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
|
751
|
+
for (const imageId of imageIdsForDisplaySet) {
|
|
752
|
+
if (!unloadedMeasurements.length) {
|
|
753
|
+
// All measurements loaded.
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const {
|
|
757
|
+
SOPInstanceUID,
|
|
758
|
+
frameNumber
|
|
759
|
+
} = metadataProvider.getUIDsFromImageID(imageId);
|
|
760
|
+
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
|
761
|
+
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
|
762
|
+
const measurement = unloadedMeasurements[j];
|
|
763
|
+
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
|
764
|
+
addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID);
|
|
765
|
+
unloadedMeasurements.splice(j, 1);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
|
772
|
+
const {
|
|
773
|
+
coords
|
|
774
|
+
} = measurement;
|
|
775
|
+
|
|
776
|
+
// NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
|
777
|
+
// Standard. But for now, we will support only one ReferenceFrameNumber.
|
|
778
|
+
const ReferencedFrameNumber = measurement.coords[0].ReferencedSOPSequence && measurement.coords[0].ReferencedSOPSequence[0]?.ReferencedFrameNumber || 1;
|
|
779
|
+
if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) return false;
|
|
780
|
+
for (let j = 0; j < coords.length; j++) {
|
|
781
|
+
const coord = coords[j];
|
|
782
|
+
const {
|
|
783
|
+
ReferencedSOPInstanceUID
|
|
784
|
+
} = coord.ReferencedSOPSequence;
|
|
785
|
+
if (ReferencedSOPInstanceUID === SOPInstanceUID) {
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function getSopClassHandlerModule(_ref) {
|
|
791
|
+
let {
|
|
792
|
+
servicesManager,
|
|
793
|
+
extensionManager
|
|
794
|
+
} = _ref;
|
|
795
|
+
const getDisplaySetsFromSeries = instances => {
|
|
796
|
+
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
|
797
|
+
};
|
|
798
|
+
return [{
|
|
799
|
+
name: SOPClassHandlerName,
|
|
800
|
+
sopClassUids,
|
|
801
|
+
getDisplaySetsFromSeries
|
|
802
|
+
}];
|
|
803
|
+
}
|
|
804
|
+
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|
805
|
+
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements);
|
|
806
|
+
const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup);
|
|
807
|
+
const mergedContentSequencesByTrackingUniqueIdentifiers = _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);
|
|
808
|
+
const measurements = [];
|
|
809
|
+
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(trackingUniqueIdentifier => {
|
|
810
|
+
const mergedContentSequence = mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier];
|
|
811
|
+
const measurement = _processMeasurement(mergedContentSequence);
|
|
812
|
+
if (measurement) {
|
|
813
|
+
measurements.push(measurement);
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
return measurements;
|
|
817
|
+
}
|
|
818
|
+
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
|
819
|
+
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
|
820
|
+
MeasurementGroups.forEach(MeasurementGroup => {
|
|
821
|
+
const ContentSequence = _getSequenceAsArray(MeasurementGroup.ContentSequence);
|
|
822
|
+
const TrackingUniqueIdentifierItem = ContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingUniqueIdentifier);
|
|
823
|
+
if (!TrackingUniqueIdentifierItem) {
|
|
824
|
+
console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.');
|
|
825
|
+
}
|
|
826
|
+
const trackingUniqueIdentifier = TrackingUniqueIdentifierItem.UID;
|
|
827
|
+
if (mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] === undefined) {
|
|
828
|
+
// Add the full ContentSequence
|
|
829
|
+
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] = [...ContentSequence];
|
|
830
|
+
} else {
|
|
831
|
+
// Add the ContentSequence minus the tracking identifier, as we have this
|
|
832
|
+
// Information in the merged ContentSequence anyway.
|
|
833
|
+
ContentSequence.forEach(item => {
|
|
834
|
+
if (item.ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.TrackingUniqueIdentifier) {
|
|
835
|
+
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier].push(item);
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
return mergedContentSequencesByTrackingUniqueIdentifiers;
|
|
841
|
+
}
|
|
842
|
+
function _processMeasurement(mergedContentSequence) {
|
|
843
|
+
if (mergedContentSequence.some(group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D')) {
|
|
844
|
+
return _processTID1410Measurement(mergedContentSequence);
|
|
845
|
+
}
|
|
846
|
+
return _processNonGeometricallyDefinedMeasurement(mergedContentSequence);
|
|
847
|
+
}
|
|
848
|
+
function _processTID1410Measurement(mergedContentSequence) {
|
|
849
|
+
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
|
850
|
+
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
|
851
|
+
|
|
852
|
+
const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD');
|
|
853
|
+
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
|
854
|
+
const TrackingIdentifierContentItem = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier);
|
|
855
|
+
if (!graphicItem) {
|
|
856
|
+
console.warn(`graphic ValueType ${graphicItem.ValueType} not currently supported, skipping annotation.`);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
|
860
|
+
const measurement = {
|
|
861
|
+
loaded: false,
|
|
862
|
+
labels: [],
|
|
863
|
+
coords: [_getCoordsFromSCOORDOrSCOORD3D(graphicItem)],
|
|
864
|
+
TrackingUniqueIdentifier: UIDREFContentItem.UID,
|
|
865
|
+
TrackingIdentifier: TrackingIdentifierContentItem.TextValue
|
|
866
|
+
};
|
|
867
|
+
NUMContentItems.forEach(item => {
|
|
868
|
+
const {
|
|
869
|
+
ConceptNameCodeSequence,
|
|
870
|
+
MeasuredValueSequence
|
|
871
|
+
} = item;
|
|
872
|
+
if (MeasuredValueSequence) {
|
|
873
|
+
measurement.labels.push(_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence));
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
return measurement;
|
|
877
|
+
}
|
|
878
|
+
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|
879
|
+
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
|
880
|
+
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
|
881
|
+
const TrackingIdentifierContentItem = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier);
|
|
882
|
+
const finding = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.Finding);
|
|
883
|
+
const findingSites = mergedContentSequence.filter(item => item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SRT && item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSite);
|
|
884
|
+
const measurement = {
|
|
885
|
+
loaded: false,
|
|
886
|
+
labels: [],
|
|
887
|
+
coords: [],
|
|
888
|
+
TrackingUniqueIdentifier: UIDREFContentItem.UID,
|
|
889
|
+
TrackingIdentifier: TrackingIdentifierContentItem.TextValue
|
|
890
|
+
};
|
|
891
|
+
if (finding && CodingSchemeDesignators.CornerstoneCodeSchemes.includes(finding.ConceptCodeSequence.CodingSchemeDesignator) && finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText) {
|
|
892
|
+
measurement.labels.push({
|
|
893
|
+
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
|
894
|
+
value: finding.ConceptCodeSequence.CodeMeaning
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// TODO -> Eventually hopefully support SNOMED or some proper code library, just free text for now.
|
|
899
|
+
if (findingSites.length) {
|
|
900
|
+
const cornerstoneFreeTextFindingSite = findingSites.find(FindingSite => CodingSchemeDesignators.CornerstoneCodeSchemes.includes(FindingSite.ConceptCodeSequence.CodingSchemeDesignator) && FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText);
|
|
901
|
+
if (cornerstoneFreeTextFindingSite) {
|
|
902
|
+
measurement.labels.push({
|
|
903
|
+
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
|
904
|
+
value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
NUMContentItems.forEach(item => {
|
|
909
|
+
const {
|
|
910
|
+
ConceptNameCodeSequence,
|
|
911
|
+
ContentSequence,
|
|
912
|
+
MeasuredValueSequence
|
|
913
|
+
} = item;
|
|
914
|
+
const {
|
|
915
|
+
ValueType
|
|
916
|
+
} = ContentSequence;
|
|
917
|
+
if (!ValueType === 'SCOORD') {
|
|
918
|
+
console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
|
|
922
|
+
if (coords) {
|
|
923
|
+
measurement.coords.push(coords);
|
|
924
|
+
}
|
|
925
|
+
if (MeasuredValueSequence) {
|
|
926
|
+
measurement.labels.push(_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence));
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
return measurement;
|
|
930
|
+
}
|
|
931
|
+
function _getCoordsFromSCOORDOrSCOORD3D(item) {
|
|
932
|
+
const {
|
|
933
|
+
ValueType,
|
|
934
|
+
RelationshipType,
|
|
935
|
+
GraphicType,
|
|
936
|
+
GraphicData
|
|
937
|
+
} = item;
|
|
938
|
+
if (!(RelationshipType == RELATIONSHIP_TYPE.INFERRED_FROM || RelationshipType == RELATIONSHIP_TYPE.CONTAINS)) {
|
|
939
|
+
console.warn(`Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM" or "CONTAINS"`);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const coords = {
|
|
943
|
+
ValueType,
|
|
944
|
+
GraphicType,
|
|
945
|
+
GraphicData
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
// ContentSequence has length of 1 as RelationshipType === 'INFERRED FROM'
|
|
949
|
+
if (ValueType === 'SCOORD') {
|
|
950
|
+
const {
|
|
951
|
+
ReferencedSOPSequence
|
|
952
|
+
} = item.ContentSequence;
|
|
953
|
+
coords.ReferencedSOPSequence = ReferencedSOPSequence;
|
|
954
|
+
} else if (ValueType === 'SCOORD3D') {
|
|
955
|
+
const {
|
|
956
|
+
ReferencedFrameOfReferenceSequence
|
|
957
|
+
} = item.ContentSequence;
|
|
958
|
+
coords.ReferencedFrameOfReferenceSequence = ReferencedFrameOfReferenceSequence;
|
|
959
|
+
}
|
|
960
|
+
return coords;
|
|
961
|
+
}
|
|
962
|
+
function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) {
|
|
963
|
+
const {
|
|
964
|
+
CodeMeaning
|
|
965
|
+
} = ConceptNameCodeSequence;
|
|
966
|
+
const {
|
|
967
|
+
NumericValue,
|
|
968
|
+
MeasurementUnitsCodeSequence
|
|
969
|
+
} = MeasuredValueSequence;
|
|
970
|
+
const {
|
|
971
|
+
CodeValue
|
|
972
|
+
} = MeasurementUnitsCodeSequence;
|
|
973
|
+
const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : '';
|
|
974
|
+
return {
|
|
975
|
+
label: CodeMeaning,
|
|
976
|
+
value: `${formatedNumericValue} ${CodeValue}`
|
|
977
|
+
}; // E.g. Long Axis: 31.0 mm
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
|
981
|
+
const ImageLibrary = ImagingMeasurementReportContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary);
|
|
982
|
+
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup);
|
|
983
|
+
const referencedImages = [];
|
|
984
|
+
_getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => {
|
|
985
|
+
const {
|
|
986
|
+
ReferencedSOPSequence
|
|
987
|
+
} = item;
|
|
988
|
+
if (!ReferencedSOPSequence) return;
|
|
989
|
+
for (const ref of _getSequenceAsArray(ReferencedSOPSequence)) {
|
|
990
|
+
if (ref.ReferencedSOPClassUID) {
|
|
991
|
+
const {
|
|
992
|
+
ReferencedSOPClassUID,
|
|
993
|
+
ReferencedSOPInstanceUID
|
|
994
|
+
} = ref;
|
|
995
|
+
referencedImages.push({
|
|
996
|
+
ReferencedSOPClassUID,
|
|
997
|
+
ReferencedSOPInstanceUID
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
return referencedImages;
|
|
1003
|
+
}
|
|
1004
|
+
function _getSequenceAsArray(sequence) {
|
|
1005
|
+
if (!sequence) return [];
|
|
1006
|
+
return Array.isArray(sequence) ? sequence : [sequence];
|
|
1007
|
+
}
|
|
1008
|
+
/* harmony default export */ const src_getSopClassHandlerModule = (getSopClassHandlerModule);
|
|
1009
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts
|
|
1010
|
+
const srProtocol = {
|
|
1011
|
+
id: '@ohif/sr',
|
|
1012
|
+
// Don't store this hanging protocol as it applies to the currently active
|
|
1013
|
+
// display set by default
|
|
1014
|
+
// cacheId: null,
|
|
1015
|
+
hasUpdatedPriorsInformation: false,
|
|
1016
|
+
name: 'SR Key Images',
|
|
1017
|
+
// Just apply this one when specifically listed
|
|
1018
|
+
protocolMatchingRules: [],
|
|
1019
|
+
toolGroupIds: ['default'],
|
|
1020
|
+
// -1 would be used to indicate active only, whereas other values are
|
|
1021
|
+
// the number of required priors referenced - so 0 means active with
|
|
1022
|
+
// 0 or more priors.
|
|
1023
|
+
numberOfPriorsReferenced: 0,
|
|
1024
|
+
// Default viewport is used to define the viewport when
|
|
1025
|
+
// additional viewports are added using the layout tool
|
|
1026
|
+
defaultViewport: {
|
|
1027
|
+
viewportOptions: {
|
|
1028
|
+
viewportType: 'stack',
|
|
1029
|
+
toolGroupId: 'default',
|
|
1030
|
+
allowUnmatchedView: true
|
|
1031
|
+
},
|
|
1032
|
+
displaySets: [{
|
|
1033
|
+
id: 'srDisplaySetId',
|
|
1034
|
+
matchedDisplaySetsIndex: -1
|
|
1035
|
+
}]
|
|
1036
|
+
},
|
|
1037
|
+
displaySetSelectors: {
|
|
1038
|
+
srDisplaySetId: {
|
|
1039
|
+
seriesMatchingRules: [{
|
|
1040
|
+
attribute: 'Modality',
|
|
1041
|
+
constraint: {
|
|
1042
|
+
equals: 'SR'
|
|
1043
|
+
}
|
|
1044
|
+
}]
|
|
1045
|
+
}
|
|
1046
|
+
},
|
|
1047
|
+
stages: [{
|
|
1048
|
+
name: 'SR Key Images',
|
|
1049
|
+
viewportStructure: {
|
|
1050
|
+
layoutType: 'grid',
|
|
1051
|
+
properties: {
|
|
1052
|
+
rows: 1,
|
|
1053
|
+
columns: 1
|
|
1054
|
+
}
|
|
1055
|
+
},
|
|
1056
|
+
viewports: [{
|
|
1057
|
+
viewportOptions: {
|
|
1058
|
+
allowUnmatchedView: true
|
|
1059
|
+
},
|
|
1060
|
+
displaySets: [{
|
|
1061
|
+
id: 'srDisplaySetId'
|
|
1062
|
+
}]
|
|
1063
|
+
}]
|
|
1064
|
+
}]
|
|
1065
|
+
};
|
|
1066
|
+
function getHangingProtocolModule() {
|
|
1067
|
+
return [{
|
|
1068
|
+
name: srProtocol.id,
|
|
1069
|
+
protocol: srProtocol
|
|
1070
|
+
}];
|
|
1071
|
+
}
|
|
1072
|
+
/* harmony default export */ const src_getHangingProtocolModule = ((/* unused pure expression or super */ null && (getHangingProtocolModule)));
|
|
1073
|
+
|
|
1074
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/onModeEnter.js
|
|
1075
|
+
|
|
1076
|
+
function onModeEnter(_ref) {
|
|
1077
|
+
let {
|
|
1078
|
+
servicesManager
|
|
1079
|
+
} = _ref;
|
|
1080
|
+
const {
|
|
1081
|
+
displaySetService
|
|
1082
|
+
} = servicesManager.services;
|
|
1083
|
+
const displaySetCache = displaySetService.getDisplaySetCache();
|
|
1084
|
+
const srDisplaySets = [...displaySetCache.values()].filter(ds => ds.SOPClassHandlerId === SOPClassHandlerId);
|
|
1085
|
+
srDisplaySets.forEach(ds => {
|
|
1086
|
+
// New mode route, allow SRs to be hydrated again
|
|
1087
|
+
ds.isHydrated = false;
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
// EXTERNAL MODULE: ../../../node_modules/dcmjs/build/dcmjs.es.js
|
|
1091
|
+
var dcmjs_es = __webpack_require__(22737);
|
|
1092
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
const {
|
|
1096
|
+
log
|
|
1097
|
+
} = src["default"];
|
|
1098
|
+
function getFilteredCornerstoneToolState(measurementData, additionalFindingTypes) {
|
|
1099
|
+
const filteredToolState = {};
|
|
1100
|
+
function addToFilteredToolState(annotation, toolType) {
|
|
1101
|
+
if (!annotation.metadata?.referencedImageId) {
|
|
1102
|
+
log.warn(`[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}`);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const imageId = annotation.metadata.referencedImageId;
|
|
1106
|
+
if (!filteredToolState[imageId]) {
|
|
1107
|
+
filteredToolState[imageId] = {};
|
|
1108
|
+
}
|
|
1109
|
+
const imageIdSpecificToolState = filteredToolState[imageId];
|
|
1110
|
+
if (!imageIdSpecificToolState[toolType]) {
|
|
1111
|
+
imageIdSpecificToolState[toolType] = {
|
|
1112
|
+
data: []
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
const measurementDataI = measurementData.find(md => md.uid === annotation.annotationUID);
|
|
1116
|
+
const toolData = imageIdSpecificToolState[toolType].data;
|
|
1117
|
+
let {
|
|
1118
|
+
finding
|
|
1119
|
+
} = measurementDataI;
|
|
1120
|
+
const findingSites = [];
|
|
1121
|
+
|
|
1122
|
+
// NOTE -> We use the CORNERSTONEJS coding schemeDesignator which we have
|
|
1123
|
+
// defined in the @cornerstonejs/adapters
|
|
1124
|
+
if (measurementDataI.label) {
|
|
1125
|
+
if (additionalFindingTypes.includes(toolType)) {
|
|
1126
|
+
finding = {
|
|
1127
|
+
CodeValue: 'CORNERSTONEFREETEXT',
|
|
1128
|
+
CodingSchemeDesignator: 'CORNERSTONEJS',
|
|
1129
|
+
CodeMeaning: measurementDataI.label
|
|
1130
|
+
};
|
|
1131
|
+
} else {
|
|
1132
|
+
findingSites.push({
|
|
1133
|
+
CodeValue: 'CORNERSTONEFREETEXT',
|
|
1134
|
+
CodingSchemeDesignator: 'CORNERSTONEJS',
|
|
1135
|
+
CodeMeaning: measurementDataI.label
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (measurementDataI.findingSites) {
|
|
1140
|
+
findingSites.push(...measurementDataI.findingSites);
|
|
1141
|
+
}
|
|
1142
|
+
const measurement = Object.assign({}, annotation, {
|
|
1143
|
+
finding,
|
|
1144
|
+
findingSites
|
|
1145
|
+
});
|
|
1146
|
+
toolData.push(measurement);
|
|
1147
|
+
}
|
|
1148
|
+
const uidFilter = measurementData.map(md => md.uid);
|
|
1149
|
+
const uids = uidFilter.slice();
|
|
1150
|
+
const annotationManager = dist_esm.annotation.state.getAnnotationManager();
|
|
1151
|
+
const framesOfReference = annotationManager.getFramesOfReference();
|
|
1152
|
+
for (let i = 0; i < framesOfReference.length; i++) {
|
|
1153
|
+
const frameOfReference = framesOfReference[i];
|
|
1154
|
+
const frameOfReferenceAnnotations = annotationManager.getAnnotations(frameOfReference);
|
|
1155
|
+
const toolTypes = Object.keys(frameOfReferenceAnnotations);
|
|
1156
|
+
for (let j = 0; j < toolTypes.length; j++) {
|
|
1157
|
+
const toolType = toolTypes[j];
|
|
1158
|
+
const annotations = frameOfReferenceAnnotations[toolType];
|
|
1159
|
+
if (annotations) {
|
|
1160
|
+
for (let k = 0; k < annotations.length; k++) {
|
|
1161
|
+
const annotation = annotations[k];
|
|
1162
|
+
const uidIndex = uids.findIndex(uid => uid === annotation.annotationUID);
|
|
1163
|
+
if (uidIndex !== -1) {
|
|
1164
|
+
addToFilteredToolState(annotation, toolType);
|
|
1165
|
+
uids.splice(uidIndex, 1);
|
|
1166
|
+
if (!uids.length) {
|
|
1167
|
+
return filteredToolState;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return filteredToolState;
|
|
1175
|
+
}
|
|
1176
|
+
/* harmony default export */ const utils_getFilteredCornerstoneToolState = (getFilteredCornerstoneToolState);
|
|
1177
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/commandsModule.js
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
const {
|
|
1184
|
+
MeasurementReport
|
|
1185
|
+
} = adapters_es.adaptersSR.Cornerstone3D;
|
|
1186
|
+
const {
|
|
1187
|
+
log: commandsModule_log
|
|
1188
|
+
} = src["default"];
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
*
|
|
1192
|
+
* @param measurementData An array of measurements from the measurements service
|
|
1193
|
+
* that you wish to serialize.
|
|
1194
|
+
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
1195
|
+
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1196
|
+
*
|
|
1197
|
+
*/
|
|
1198
|
+
const _generateReport = function (measurementData, additionalFindingTypes) {
|
|
1199
|
+
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
1200
|
+
const filteredToolState = utils_getFilteredCornerstoneToolState(measurementData, additionalFindingTypes);
|
|
1201
|
+
const report = MeasurementReport.generateReport(filteredToolState, core_dist_esm.metaData, core_dist_esm.utilities.worldToImageCoords, options);
|
|
1202
|
+
const {
|
|
1203
|
+
dataset
|
|
1204
|
+
} = report;
|
|
1205
|
+
|
|
1206
|
+
// Set the default character set as UTF-8
|
|
1207
|
+
// https://dicom.innolitics.com/ciods/nm-image/sop-common/00080005
|
|
1208
|
+
if (typeof dataset.SpecificCharacterSet === 'undefined') {
|
|
1209
|
+
dataset.SpecificCharacterSet = 'ISO_IR 192';
|
|
1210
|
+
}
|
|
1211
|
+
return dataset;
|
|
1212
|
+
};
|
|
1213
|
+
const commandsModule = _ref => {
|
|
1214
|
+
let {} = _ref;
|
|
1215
|
+
const actions = {
|
|
1216
|
+
/**
|
|
1217
|
+
*
|
|
1218
|
+
* @param measurementData An array of measurements from the measurements service
|
|
1219
|
+
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
1220
|
+
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1221
|
+
* as opposed to Finding Sites.
|
|
1222
|
+
* that you wish to serialize.
|
|
1223
|
+
*/
|
|
1224
|
+
downloadReport: _ref2 => {
|
|
1225
|
+
let {
|
|
1226
|
+
measurementData,
|
|
1227
|
+
additionalFindingTypes,
|
|
1228
|
+
options = {}
|
|
1229
|
+
} = _ref2;
|
|
1230
|
+
const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options);
|
|
1231
|
+
const reportBlob = dcmjs_es["default"].data.datasetToBlob(srDataset);
|
|
1232
|
+
|
|
1233
|
+
//Create a URL for the binary.
|
|
1234
|
+
var objectUrl = URL.createObjectURL(reportBlob);
|
|
1235
|
+
window.location.assign(objectUrl);
|
|
1236
|
+
},
|
|
1237
|
+
/**
|
|
1238
|
+
*
|
|
1239
|
+
* @param measurementData An array of measurements from the measurements service
|
|
1240
|
+
* that you wish to serialize.
|
|
1241
|
+
* @param dataSource The dataSource that you wish to use to persist the data.
|
|
1242
|
+
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
1243
|
+
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1244
|
+
* @return The naturalized report
|
|
1245
|
+
*/
|
|
1246
|
+
storeMeasurements: async _ref3 => {
|
|
1247
|
+
let {
|
|
1248
|
+
measurementData,
|
|
1249
|
+
dataSource,
|
|
1250
|
+
additionalFindingTypes,
|
|
1251
|
+
options = {}
|
|
1252
|
+
} = _ref3;
|
|
1253
|
+
// Use the @cornerstonejs adapter for converting to/from DICOM
|
|
1254
|
+
// But it is good enough for now whilst we only have cornerstone as a datasource.
|
|
1255
|
+
commandsModule_log.info('[DICOMSR] storeMeasurements');
|
|
1256
|
+
if (!dataSource || !dataSource.store || !dataSource.store.dicom) {
|
|
1257
|
+
commandsModule_log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!');
|
|
1258
|
+
return Promise.reject({});
|
|
1259
|
+
}
|
|
1260
|
+
try {
|
|
1261
|
+
const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options);
|
|
1262
|
+
const {
|
|
1263
|
+
StudyInstanceUID,
|
|
1264
|
+
ContentSequence
|
|
1265
|
+
} = naturalizedReport;
|
|
1266
|
+
// The content sequence has 5 or more elements, of which
|
|
1267
|
+
// the `[4]` element contains the annotation data, so this is
|
|
1268
|
+
// checking that there is some annotation data present.
|
|
1269
|
+
if (!ContentSequence?.[4].ContentSequence?.length) {
|
|
1270
|
+
console.log('naturalizedReport missing imaging content', naturalizedReport);
|
|
1271
|
+
throw new Error('Invalid report, no content');
|
|
1272
|
+
}
|
|
1273
|
+
await dataSource.store.dicom(naturalizedReport);
|
|
1274
|
+
if (StudyInstanceUID) {
|
|
1275
|
+
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// The "Mode" route listens for DicomMetadataStore changes
|
|
1279
|
+
// When a new instance is added, it listens and
|
|
1280
|
+
// automatically calls makeDisplaySets
|
|
1281
|
+
src.DicomMetadataStore.addInstances([naturalizedReport], true);
|
|
1282
|
+
return naturalizedReport;
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
console.warn(error);
|
|
1285
|
+
commandsModule_log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`);
|
|
1286
|
+
throw new Error(error.message || 'Error while saving the measurements.');
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
const definitions = {
|
|
1291
|
+
downloadReport: {
|
|
1292
|
+
commandFn: actions.downloadReport,
|
|
1293
|
+
storeContexts: [],
|
|
1294
|
+
options: {}
|
|
1295
|
+
},
|
|
1296
|
+
storeMeasurements: {
|
|
1297
|
+
commandFn: actions.storeMeasurements,
|
|
1298
|
+
storeContexts: [],
|
|
1299
|
+
options: {}
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
return {
|
|
1303
|
+
actions,
|
|
1304
|
+
definitions,
|
|
1305
|
+
defaultContext: 'CORNERSTONE_STRUCTURED_REPORT'
|
|
1306
|
+
};
|
|
1307
|
+
};
|
|
1308
|
+
/* harmony default export */ const src_commandsModule = (commandsModule);
|
|
1309
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts
|
|
1310
|
+
|
|
1311
|
+
function addToolInstance(name, toolClass, configuration) {
|
|
1312
|
+
class InstanceClass extends toolClass {}
|
|
1313
|
+
InstanceClass.toolName = name;
|
|
1314
|
+
(0,dist_esm.addTool)(InstanceClass);
|
|
1315
|
+
}
|
|
1316
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/init.ts
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* @param {object} configuration
|
|
1324
|
+
*/
|
|
1325
|
+
function init(_ref) {
|
|
1326
|
+
let {
|
|
1327
|
+
configuration = {}
|
|
1328
|
+
} = _ref;
|
|
1329
|
+
(0,dist_esm.addTool)(DICOMSRDisplayTool);
|
|
1330
|
+
addToolInstance(tools_toolNames.SRLength, dist_esm.LengthTool, {});
|
|
1331
|
+
addToolInstance(tools_toolNames.SRBidirectional, dist_esm.BidirectionalTool);
|
|
1332
|
+
addToolInstance(tools_toolNames.SREllipticalROI, dist_esm.EllipticalROITool);
|
|
1333
|
+
addToolInstance(tools_toolNames.SRCircleROI, dist_esm.CircleROITool);
|
|
1334
|
+
addToolInstance(tools_toolNames.SRArrowAnnotate, dist_esm.ArrowAnnotateTool);
|
|
1335
|
+
addToolInstance(tools_toolNames.SRAngle, dist_esm.AngleTool);
|
|
1336
|
+
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
|
|
1337
|
+
addToolInstance(tools_toolNames.SRCobbAngle, dist_esm.CobbAngleTool);
|
|
1338
|
+
// TODO - fix the rehydration of Freehand, as it throws an exception
|
|
1339
|
+
// on a missing polyline. The fix is probably in CS3D
|
|
1340
|
+
addToolInstance(tools_toolNames.SRPlanarFreehandROI, dist_esm.PlanarFreehandROITool);
|
|
1341
|
+
|
|
1342
|
+
// Modify annotation tools to use dashed lines on SR
|
|
1343
|
+
const dashedLine = {
|
|
1344
|
+
lineDash: '4,4'
|
|
1345
|
+
};
|
|
1346
|
+
dist_esm.annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
|
|
1347
|
+
SRLength: dashedLine,
|
|
1348
|
+
SRBidirectional: dashedLine,
|
|
1349
|
+
SREllipticalROI: dashedLine,
|
|
1350
|
+
SRCircleROI: dashedLine,
|
|
1351
|
+
SRArrowAnnotate: dashedLine,
|
|
1352
|
+
SRCobbAngle: dashedLine,
|
|
1353
|
+
SRAngle: dashedLine,
|
|
1354
|
+
SRPlanarFreehandROI: dashedLine,
|
|
1355
|
+
global: {}
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js + 1 modules
|
|
1359
|
+
var hydrateStructuredReport = __webpack_require__(68525);
|
|
1360
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts
|
|
1361
|
+
|
|
1362
|
+
const createReferencedImageDisplaySet_ImageSet = src.classes.ImageSet;
|
|
1363
|
+
const findInstance = (measurement, displaySetService) => {
|
|
1364
|
+
const {
|
|
1365
|
+
displaySetInstanceUID,
|
|
1366
|
+
ReferencedSOPInstanceUID: sopUid
|
|
1367
|
+
} = measurement;
|
|
1368
|
+
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
|
1369
|
+
if (!referencedDisplaySet.images) return;
|
|
1370
|
+
return referencedDisplaySet.images.find(it => it.SOPInstanceUID === sopUid);
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
/** Finds references to display sets inside the measurements
|
|
1374
|
+
* contained within the provided display set.
|
|
1375
|
+
* @return an array of instances referenced.
|
|
1376
|
+
*/
|
|
1377
|
+
const findReferencedInstances = (displaySetService, displaySet) => {
|
|
1378
|
+
const instances = [];
|
|
1379
|
+
const instanceById = {};
|
|
1380
|
+
for (const measurement of displaySet.measurements) {
|
|
1381
|
+
const {
|
|
1382
|
+
imageId
|
|
1383
|
+
} = measurement;
|
|
1384
|
+
if (!imageId) continue;
|
|
1385
|
+
if (instanceById[imageId]) continue;
|
|
1386
|
+
const instance = findInstance(measurement, displaySetService);
|
|
1387
|
+
if (!instance) {
|
|
1388
|
+
console.log('Measurement', measurement, 'had no instances found');
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
instanceById[imageId] = instance;
|
|
1392
|
+
instances.push(instance);
|
|
1393
|
+
}
|
|
1394
|
+
return instances;
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Creates a new display set containing a single image instance for each
|
|
1399
|
+
* referenced image.
|
|
1400
|
+
*
|
|
1401
|
+
* @param displaySetService
|
|
1402
|
+
* @param displaySet - containing measurements referencing images.
|
|
1403
|
+
* @returns A new (registered/active) display set containing the referenced images
|
|
1404
|
+
*/
|
|
1405
|
+
const createReferencedImageDisplaySet = (displaySetService, displaySet) => {
|
|
1406
|
+
const instances = findReferencedInstances(displaySetService, displaySet);
|
|
1407
|
+
// This will be a member function of the created image set
|
|
1408
|
+
const updateInstances = function () {
|
|
1409
|
+
this.images.splice(0, this.images.length, ...findReferencedInstances(displaySetService, displaySet));
|
|
1410
|
+
this.numImageFrames = this.images.length;
|
|
1411
|
+
};
|
|
1412
|
+
const imageSet = new createReferencedImageDisplaySet_ImageSet(instances);
|
|
1413
|
+
const instance = instances[0];
|
|
1414
|
+
imageSet.setAttributes({
|
|
1415
|
+
displaySetInstanceUID: imageSet.uid,
|
|
1416
|
+
// create a local alias for the imageSet UID
|
|
1417
|
+
SeriesDate: instance.SeriesDate,
|
|
1418
|
+
SeriesTime: instance.SeriesTime,
|
|
1419
|
+
SeriesInstanceUID: imageSet.uid,
|
|
1420
|
+
StudyInstanceUID: instance.StudyInstanceUID,
|
|
1421
|
+
SeriesNumber: instance.SeriesNumber || 0,
|
|
1422
|
+
SOPClassUID: instance.SOPClassUID,
|
|
1423
|
+
SeriesDescription: `${displaySet.SeriesDescription} KO ${displaySet.instance.SeriesNumber}`,
|
|
1424
|
+
Modality: 'KO',
|
|
1425
|
+
isMultiFrame: false,
|
|
1426
|
+
numImageFrames: instances.length,
|
|
1427
|
+
SOPClassHandlerId: `@ohif/extension-default.sopClassHandlerModule.stack`,
|
|
1428
|
+
isReconstructable: false,
|
|
1429
|
+
// This object is made of multiple instances from other series
|
|
1430
|
+
isCompositeStack: true,
|
|
1431
|
+
madeInClient: true,
|
|
1432
|
+
excludeFromThumbnailBrowser: true,
|
|
1433
|
+
updateInstances
|
|
1434
|
+
});
|
|
1435
|
+
displaySetService.addDisplaySets(imageSet);
|
|
1436
|
+
return imageSet;
|
|
1437
|
+
};
|
|
1438
|
+
/* harmony default export */ const utils_createReferencedImageDisplaySet = (createReferencedImageDisplaySet);
|
|
1439
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/index.tsx
|
|
1440
|
+
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); }
|
|
1441
|
+
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
|
|
1448
|
+
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
const Component = /*#__PURE__*/react.lazy(() => {
|
|
1452
|
+
return __webpack_require__.e(/* import() */ 953).then(__webpack_require__.bind(__webpack_require__, 27953));
|
|
1453
|
+
});
|
|
1454
|
+
const OHIFCornerstoneSRViewport = props => {
|
|
1455
|
+
return /*#__PURE__*/react.createElement(react.Suspense, {
|
|
1456
|
+
fallback: /*#__PURE__*/react.createElement("div", null, "Loading...")
|
|
1457
|
+
}, /*#__PURE__*/react.createElement(Component, props));
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
*
|
|
1462
|
+
*/
|
|
1463
|
+
const dicomSRExtension = {
|
|
1464
|
+
/**
|
|
1465
|
+
* Only required property. Should be a unique value across all extensions.
|
|
1466
|
+
*/
|
|
1467
|
+
id: id,
|
|
1468
|
+
onModeEnter: onModeEnter,
|
|
1469
|
+
preRegistration: init,
|
|
1470
|
+
/**
|
|
1471
|
+
*
|
|
1472
|
+
*
|
|
1473
|
+
* @param {object} [configuration={}]
|
|
1474
|
+
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
|
|
1475
|
+
*/
|
|
1476
|
+
getViewportModule(_ref) {
|
|
1477
|
+
let {
|
|
1478
|
+
servicesManager,
|
|
1479
|
+
extensionManager
|
|
1480
|
+
} = _ref;
|
|
1481
|
+
const ExtendedOHIFCornerstoneSRViewport = props => {
|
|
1482
|
+
return /*#__PURE__*/react.createElement(OHIFCornerstoneSRViewport, _extends({
|
|
1483
|
+
servicesManager: servicesManager,
|
|
1484
|
+
extensionManager: extensionManager
|
|
1485
|
+
}, props));
|
|
1486
|
+
};
|
|
1487
|
+
return [{
|
|
1488
|
+
name: 'dicom-sr',
|
|
1489
|
+
component: ExtendedOHIFCornerstoneSRViewport
|
|
1490
|
+
}];
|
|
1491
|
+
},
|
|
1492
|
+
getCommandsModule: src_commandsModule,
|
|
1493
|
+
getSopClassHandlerModule: src_getSopClassHandlerModule,
|
|
1494
|
+
// Include dynmically computed values such as toolNames not known till instantiation
|
|
1495
|
+
getUtilityModule(_ref2) {
|
|
1496
|
+
let {
|
|
1497
|
+
servicesManager
|
|
1498
|
+
} = _ref2;
|
|
1499
|
+
return [{
|
|
1500
|
+
name: 'tools',
|
|
1501
|
+
exports: {
|
|
1502
|
+
toolNames: tools_toolNames
|
|
1503
|
+
}
|
|
1504
|
+
}];
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
/* harmony default export */ const cornerstone_dicom_sr_src = (dicomSRExtension);
|
|
1508
|
+
|
|
1509
|
+
// Put static exports here so they can be type checked
|
|
1510
|
+
|
|
1511
|
+
|
|
1512
|
+
/***/ }),
|
|
1513
|
+
|
|
1514
|
+
/***/ 94709:
|
|
1515
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
1516
|
+
|
|
1517
|
+
"use strict";
|
|
1518
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
1519
|
+
/* harmony export */ "l2": () => (/* binding */ setTrackingUniqueIdentifiersForElement),
|
|
1520
|
+
/* harmony export */ "yR": () => (/* binding */ getTrackingUniqueIdentifiersForElement)
|
|
1521
|
+
/* harmony export */ });
|
|
1522
|
+
/* unused harmony export setActiveTrackingUniqueIdentifierForElement */
|
|
1523
|
+
/* harmony import */ var _cornerstonejs_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(77331);
|
|
1524
|
+
|
|
1525
|
+
const state = {
|
|
1526
|
+
TrackingUniqueIdentifier: null,
|
|
1527
|
+
trackingIdentifiersByViewportId: {}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* This file is being used to store the per-viewport state of the SR tools,
|
|
1532
|
+
* Since, all the toolStates are added to the cornerstoneTools, when displaying the SRTools,
|
|
1533
|
+
* if there are two viewports rendering the same imageId, we don't want to show
|
|
1534
|
+
* the same SR annotation twice on irrelevant viewport, hence, we are storing the state
|
|
1535
|
+
* of the SR tools in state here, so that we can filter them later.
|
|
1536
|
+
*/
|
|
1537
|
+
|
|
1538
|
+
function setTrackingUniqueIdentifiersForElement(element, trackingUniqueIdentifiers) {
|
|
1539
|
+
let activeIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
1540
|
+
const enabledElement = (0,_cornerstonejs_core__WEBPACK_IMPORTED_MODULE_0__.getEnabledElement)(element);
|
|
1541
|
+
const {
|
|
1542
|
+
viewport
|
|
1543
|
+
} = enabledElement;
|
|
1544
|
+
state.trackingIdentifiersByViewportId[viewport.id] = {
|
|
1545
|
+
trackingUniqueIdentifiers,
|
|
1546
|
+
activeIndex
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
function setActiveTrackingUniqueIdentifierForElement(element, TrackingUniqueIdentifier) {
|
|
1550
|
+
const enabledElement = getEnabledElement(element);
|
|
1551
|
+
const {
|
|
1552
|
+
viewport
|
|
1553
|
+
} = enabledElement;
|
|
1554
|
+
const trackingIdentifiersForElement = state.trackingIdentifiersByViewportId[viewport.id];
|
|
1555
|
+
if (trackingIdentifiersForElement) {
|
|
1556
|
+
const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex(tuid => tuid === TrackingUniqueIdentifier);
|
|
1557
|
+
trackingIdentifiersForElement.activeIndex = activeIndex;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function getTrackingUniqueIdentifiersForElement(element) {
|
|
1561
|
+
const enabledElement = (0,_cornerstonejs_core__WEBPACK_IMPORTED_MODULE_0__.getEnabledElement)(element);
|
|
1562
|
+
const {
|
|
1563
|
+
viewport
|
|
1564
|
+
} = enabledElement;
|
|
1565
|
+
if (state.trackingIdentifiersByViewportId[viewport.id]) {
|
|
1566
|
+
return state.trackingIdentifiersByViewportId[viewport.id];
|
|
1567
|
+
}
|
|
1568
|
+
return {
|
|
1569
|
+
trackingUniqueIdentifiers: []
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
|
|
1574
|
+
/***/ }),
|
|
1575
|
+
|
|
1576
|
+
/***/ 68525:
|
|
1577
|
+
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
1578
|
+
|
|
1579
|
+
"use strict";
|
|
1580
|
+
|
|
1581
|
+
// EXPORTS
|
|
1582
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
1583
|
+
"Z": () => (/* binding */ hydrateStructuredReport)
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 335 modules
|
|
1587
|
+
var esm = __webpack_require__(77331);
|
|
1588
|
+
// EXTERNAL MODULE: ../../core/src/index.ts + 101 modules
|
|
1589
|
+
var src = __webpack_require__(48501);
|
|
1590
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js
|
|
1591
|
+
/**
|
|
1592
|
+
* Extracts the label from the toolData imported from dcmjs. We need to do this
|
|
1593
|
+
* as dcmjs does not depeend on OHIF/the measurementService, it just produces data for cornestoneTools.
|
|
1594
|
+
* This optional data is available for the consumer to process if they wish to.
|
|
1595
|
+
* @param {object} toolData The tooldata relating to the
|
|
1596
|
+
*
|
|
1597
|
+
* @returns {string} The extracted label.
|
|
1598
|
+
*/
|
|
1599
|
+
function getLabelFromDCMJSImportedToolData(toolData) {
|
|
1600
|
+
const {
|
|
1601
|
+
findingSites = [],
|
|
1602
|
+
finding
|
|
1603
|
+
} = toolData;
|
|
1604
|
+
let freeTextLabel = findingSites.find(fs => fs.CodeValue === 'CORNERSTONEFREETEXT');
|
|
1605
|
+
if (freeTextLabel) {
|
|
1606
|
+
return freeTextLabel.CodeMeaning;
|
|
1607
|
+
}
|
|
1608
|
+
if (finding && finding.CodeValue === 'CORNERSTONEFREETEXT') {
|
|
1609
|
+
return finding.CodeMeaning;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/@cornerstonejs/adapters.es.js
|
|
1613
|
+
var adapters_es = __webpack_require__(4606);
|
|
1614
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js
|
|
1615
|
+
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
|
|
1619
|
+
const {
|
|
1620
|
+
guid
|
|
1621
|
+
} = src["default"].utils;
|
|
1622
|
+
const {
|
|
1623
|
+
MeasurementReport,
|
|
1624
|
+
CORNERSTONE_3D_TAG
|
|
1625
|
+
} = adapters_es.adaptersSR.Cornerstone3D;
|
|
1626
|
+
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
|
1627
|
+
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|
1628
|
+
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
|
1629
|
+
const convertCode = (codingValues, code) => {
|
|
1630
|
+
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') return;
|
|
1631
|
+
const ref = `${code.CodingSchemeDesignator}:${code.CodeValue}`;
|
|
1632
|
+
const ret = {
|
|
1633
|
+
...codingValues[ref],
|
|
1634
|
+
ref,
|
|
1635
|
+
...code,
|
|
1636
|
+
text: code.CodeMeaning
|
|
1637
|
+
};
|
|
1638
|
+
return ret;
|
|
1639
|
+
};
|
|
1640
|
+
const convertSites = (codingValues, sites) => {
|
|
1641
|
+
if (!sites || !sites.length) return;
|
|
1642
|
+
const ret = [];
|
|
1643
|
+
// Do as a loop to convert away from Proxy instances
|
|
1644
|
+
for (let i = 0; i < sites.length; i++) {
|
|
1645
|
+
// Deal with irregular conversion from dcmjs
|
|
1646
|
+
const site = convertCode(codingValues, sites[i][0] || sites[i]);
|
|
1647
|
+
if (site) ret.push(site);
|
|
1648
|
+
}
|
|
1649
|
+
return ret.length && ret || undefined;
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* Hydrates a structured report, for default viewports.
|
|
1654
|
+
*
|
|
1655
|
+
*/
|
|
1656
|
+
function hydrateStructuredReport(_ref, displaySetInstanceUID) {
|
|
1657
|
+
let {
|
|
1658
|
+
servicesManager,
|
|
1659
|
+
extensionManager
|
|
1660
|
+
} = _ref;
|
|
1661
|
+
const dataSource = extensionManager.getActiveDataSource()[0];
|
|
1662
|
+
const {
|
|
1663
|
+
measurementService,
|
|
1664
|
+
displaySetService,
|
|
1665
|
+
customizationService
|
|
1666
|
+
} = servicesManager.services;
|
|
1667
|
+
const codingValues = customizationService.getCustomization('codingValues', {});
|
|
1668
|
+
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
|
1669
|
+
|
|
1670
|
+
// TODO -> We should define a strict versioning somewhere.
|
|
1671
|
+
const mappings = measurementService.getSourceMappings(CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION);
|
|
1672
|
+
if (!mappings || !mappings.length) {
|
|
1673
|
+
throw new Error(`Attempting to hydrate measurements service when no mappings present. This shouldn't be reached.`);
|
|
1674
|
+
}
|
|
1675
|
+
const instance = src.DicomMetadataStore.getInstance(displaySet.StudyInstanceUID, displaySet.SeriesInstanceUID, displaySet.SOPInstanceUID);
|
|
1676
|
+
const sopInstanceUIDToImageId = {};
|
|
1677
|
+
const imageIdsForToolState = {};
|
|
1678
|
+
displaySet.measurements.forEach(measurement => {
|
|
1679
|
+
const {
|
|
1680
|
+
ReferencedSOPInstanceUID,
|
|
1681
|
+
imageId,
|
|
1682
|
+
frameNumber
|
|
1683
|
+
} = measurement;
|
|
1684
|
+
if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) {
|
|
1685
|
+
sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId;
|
|
1686
|
+
imageIdsForToolState[ReferencedSOPInstanceUID] = [];
|
|
1687
|
+
}
|
|
1688
|
+
if (!imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber]) {
|
|
1689
|
+
imageIdsForToolState[ReferencedSOPInstanceUID][frameNumber] = imageId;
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
const datasetToUse = _mapLegacyDataSet(instance);
|
|
1693
|
+
|
|
1694
|
+
// Use dcmjs to generate toolState.
|
|
1695
|
+
const storedMeasurementByAnnotationType = MeasurementReport.generateToolState(datasetToUse,
|
|
1696
|
+
// NOTE: we need to pass in the imageIds to dcmjs since the we use them
|
|
1697
|
+
// for the imageToWorld transformation. The following assumes that the order
|
|
1698
|
+
// that measurements were added to the display set are the same order as
|
|
1699
|
+
// the measurementGroups in the instance.
|
|
1700
|
+
sopInstanceUIDToImageId, esm.utilities.imageToWorldCoords, esm.metaData);
|
|
1701
|
+
|
|
1702
|
+
// Filter what is found by DICOM SR to measurements we support.
|
|
1703
|
+
const mappingDefinitions = mappings.map(m => m.annotationType);
|
|
1704
|
+
const hydratableMeasurementsInSR = {};
|
|
1705
|
+
Object.keys(storedMeasurementByAnnotationType).forEach(key => {
|
|
1706
|
+
if (mappingDefinitions.includes(key)) {
|
|
1707
|
+
hydratableMeasurementsInSR[key] = storedMeasurementByAnnotationType[key];
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
// Set the series touched as tracked.
|
|
1712
|
+
const imageIds = [];
|
|
1713
|
+
|
|
1714
|
+
// TODO: notification if no hydratable?
|
|
1715
|
+
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
|
1716
|
+
const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType];
|
|
1717
|
+
toolDataForAnnotationType.forEach(toolData => {
|
|
1718
|
+
// Add the measurement to toolState
|
|
1719
|
+
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
|
1720
|
+
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
|
1721
|
+
// in the wrong value.
|
|
1722
|
+
const frameNumber = toolData.annotation.data && toolData.annotation.data.frameNumber || 1;
|
|
1723
|
+
const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
|
1724
|
+
if (!imageIds.includes(imageId)) {
|
|
1725
|
+
imageIds.push(imageId);
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
});
|
|
1729
|
+
let targetStudyInstanceUID;
|
|
1730
|
+
const SeriesInstanceUIDs = [];
|
|
1731
|
+
for (let i = 0; i < imageIds.length; i++) {
|
|
1732
|
+
const imageId = imageIds[i];
|
|
1733
|
+
const {
|
|
1734
|
+
SeriesInstanceUID,
|
|
1735
|
+
StudyInstanceUID
|
|
1736
|
+
} = esm.metaData.get('instance', imageId);
|
|
1737
|
+
if (!SeriesInstanceUIDs.includes(SeriesInstanceUID)) {
|
|
1738
|
+
SeriesInstanceUIDs.push(SeriesInstanceUID);
|
|
1739
|
+
}
|
|
1740
|
+
if (!targetStudyInstanceUID) {
|
|
1741
|
+
targetStudyInstanceUID = StudyInstanceUID;
|
|
1742
|
+
} else if (targetStudyInstanceUID !== StudyInstanceUID) {
|
|
1743
|
+
console.warn('NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.');
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
Object.keys(hydratableMeasurementsInSR).forEach(annotationType => {
|
|
1747
|
+
const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType];
|
|
1748
|
+
toolDataForAnnotationType.forEach(toolData => {
|
|
1749
|
+
// Add the measurement to toolState
|
|
1750
|
+
// dcmjs and Cornerstone3D has structural defect in supporting multi-frame
|
|
1751
|
+
// files, and looking up the imageId from sopInstanceUIDToImageId results
|
|
1752
|
+
// in the wrong value.
|
|
1753
|
+
const frameNumber = toolData.annotation.data && toolData.annotation.data.frameNumber || 1;
|
|
1754
|
+
const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid];
|
|
1755
|
+
toolData.uid = guid();
|
|
1756
|
+
const instance = esm.metaData.get('instance', imageId);
|
|
1757
|
+
const {
|
|
1758
|
+
FrameOfReferenceUID
|
|
1759
|
+
// SOPInstanceUID,
|
|
1760
|
+
// SeriesInstanceUID,
|
|
1761
|
+
// StudyInstanceUID,
|
|
1762
|
+
} = instance;
|
|
1763
|
+
const annotation = {
|
|
1764
|
+
annotationUID: toolData.annotation.annotationUID,
|
|
1765
|
+
data: toolData.annotation.data,
|
|
1766
|
+
metadata: {
|
|
1767
|
+
toolName: annotationType,
|
|
1768
|
+
referencedImageId: imageId,
|
|
1769
|
+
FrameOfReferenceUID
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
const source = measurementService.getSource(CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION);
|
|
1773
|
+
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
|
|
1774
|
+
annotation.data.finding = convertCode(codingValues, toolData.finding?.[0]);
|
|
1775
|
+
annotation.data.findingSites = convertSites(codingValues, toolData.findingSites);
|
|
1776
|
+
annotation.data.site = annotation.data.findingSites?.[0];
|
|
1777
|
+
const matchingMapping = mappings.find(m => m.annotationType === annotationType);
|
|
1778
|
+
measurementService.addRawMeasurement(source, annotationType, {
|
|
1779
|
+
annotation
|
|
1780
|
+
}, matchingMapping.toMeasurementSchema, dataSource);
|
|
1781
|
+
if (!imageIds.includes(imageId)) {
|
|
1782
|
+
imageIds.push(imageId);
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
});
|
|
1786
|
+
displaySet.isHydrated = true;
|
|
1787
|
+
return {
|
|
1788
|
+
StudyInstanceUID: targetStudyInstanceUID,
|
|
1789
|
+
SeriesInstanceUIDs
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
function _mapLegacyDataSet(dataset) {
|
|
1793
|
+
const REPORT = 'Imaging Measurements';
|
|
1794
|
+
const GROUP = 'Measurement Group';
|
|
1795
|
+
const TRACKING_IDENTIFIER = 'Tracking Identifier';
|
|
1796
|
+
|
|
1797
|
+
// Identify the Imaging Measurements
|
|
1798
|
+
const imagingMeasurementContent = toArray(dataset.ContentSequence).find(codeMeaningEquals(REPORT));
|
|
1799
|
+
|
|
1800
|
+
// Retrieve the Measurements themselves
|
|
1801
|
+
const measurementGroups = toArray(imagingMeasurementContent.ContentSequence).filter(codeMeaningEquals(GROUP));
|
|
1802
|
+
|
|
1803
|
+
// For each of the supported measurement types, compute the measurement data
|
|
1804
|
+
const measurementData = {};
|
|
1805
|
+
const cornerstoneToolClasses = MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
|
1806
|
+
const registeredToolClasses = [];
|
|
1807
|
+
Object.keys(cornerstoneToolClasses).forEach(key => {
|
|
1808
|
+
registeredToolClasses.push(cornerstoneToolClasses[key]);
|
|
1809
|
+
measurementData[key] = [];
|
|
1810
|
+
});
|
|
1811
|
+
measurementGroups.forEach((measurementGroup, index) => {
|
|
1812
|
+
const measurementGroupContentSequence = toArray(measurementGroup.ContentSequence);
|
|
1813
|
+
const TrackingIdentifierGroup = measurementGroupContentSequence.find(contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER);
|
|
1814
|
+
const TrackingIdentifier = TrackingIdentifierGroup.TextValue;
|
|
1815
|
+
let [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
|
|
1816
|
+
if (supportedLegacyCornerstoneTags.includes(cornerstoneTag)) {
|
|
1817
|
+
cornerstoneTag = CORNERSTONE_3D_TAG;
|
|
1818
|
+
}
|
|
1819
|
+
const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`;
|
|
1820
|
+
TrackingIdentifierGroup.TextValue = mappedTrackingIdentifier;
|
|
1821
|
+
});
|
|
1822
|
+
return dataset;
|
|
1823
|
+
}
|
|
1824
|
+
const toArray = function (x) {
|
|
1825
|
+
return Array.isArray(x) ? x : [x];
|
|
1826
|
+
};
|
|
1827
|
+
const codeMeaningEquals = codeMeaningName => {
|
|
1828
|
+
return contentItem => {
|
|
1829
|
+
return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName;
|
|
1830
|
+
};
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1833
|
+
/***/ }),
|
|
1834
|
+
|
|
1835
|
+
/***/ 78753:
|
|
1836
|
+
/***/ (() => {
|
|
1837
|
+
|
|
1838
|
+
/* (ignored) */
|
|
1839
|
+
|
|
1840
|
+
/***/ })
|
|
1841
|
+
|
|
1842
|
+
}]);
|