@ohif/app 3.9.0-beta.98 → 3.9.0-beta.99
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/{1169.bundle.4622fde2805592aad5d0.js → 1169.bundle.787863a90ed935004d9d.js} +2 -1
- package/dist/{4842.bundle.6188ba4a4aca12aaf7d1.js → 1841.bundle.b644430a7b443b715aee.js} +11 -11
- package/dist/1841.css +3 -0
- package/dist/{2119.bundle.6aca0cf4fc5db62d77b9.js → 2119.bundle.112adcabafef89bc3d13.js} +3 -3
- package/dist/2173.css +3 -0
- package/dist/{2650.bundle.feb3b241748314884ae4.js → 2650.bundle.74fd325955c61b0311ec.js} +793 -30
- package/dist/{3117.bundle.463d5fa4671a93939d36.js → 3117.bundle.d76671aced0d2669817a.js} +2 -562
- package/dist/{962.bundle.b60b094cbfd0486819ce.js → 3846.bundle.559405e95050bff1bdb8.js} +111 -75
- package/dist/{3962.bundle.9a1d66291b3070873892.js → 3962.bundle.bbb70fcdf211b7044025.js} +2 -2
- package/dist/{4210.bundle.ccccde45071c91a381b5.js → 4210.bundle.a41a375b496c7019fbd9.js} +3 -3
- package/dist/{7360.bundle.70420db3a8b2396a74ca.js → 4566.bundle.ec9936198e788f67bd79.js} +1210 -776
- package/dist/{5888.bundle.687c9173dc3dbca67b6c.js → 5888.bundle.345f92c3ccc69e0c4543.js} +4 -6
- package/dist/{6552.bundle.286dda309441db5b56c1.js → 6552.bundle.87f348d1180c24315677.js} +35 -23
- package/dist/{3482.bundle.0b9c4d64f4b14af4ee0a.js → 6558.bundle.b68e53ad9d4cfabea140.js} +44 -52
- package/dist/6558.css +3 -0
- package/dist/{6904.bundle.da0d83fcd48a48cfaac1.js → 6904.bundle.2daa01969c25260fb947.js} +51 -29
- package/dist/{818.bundle.b6027359846cc90e32ff.js → 818.bundle.54e9bb8d6df15d139927.js} +10 -0
- package/dist/{8714.bundle.36331faa35db985a786c.js → 8714.bundle.9915effb1c16f334a69b.js} +17 -5
- package/dist/{3497.bundle.512610422a2862584c55.js → 896.bundle.0300bc2ea91c7160248d.js} +1 -560
- package/dist/{8993.bundle.23cd5f8560800a97d798.js → 8993.bundle.fc59969a3dfc25255a3a.js} +735 -26
- package/dist/{8999.bundle.a8701d5a00ceb84bab64.js → 8999.bundle.e936840b7caf9dae422e.js} +3 -3
- package/dist/{9579.bundle.f9c9be0c5ab6cddbde3d.js → 9579.bundle.8bb193d6ff0f63a19c72.js} +11 -2
- package/dist/{7913.bundle.4f259b2f5f30efeaf121.js → 9788.bundle.a2f2b48921c47b54853c.js} +18 -24
- package/dist/9788.css +3 -0
- package/dist/{app.bundle.289e1ee9fe757434bbc4.js → app.bundle.21e229cd4d308d88ddf4.js} +2882 -2782
- package/dist/app.bundle.css +2 -2
- package/dist/cornerstoneDICOMImageLoader.min.js +1 -1
- package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -1
- package/dist/index.html +1 -1
- package/dist/{polySeg.bundle.0a70e65fe89169ec7508.js → polySeg.bundle.229cf4761a5e2c225748.js} +3 -3
- package/dist/{suv-peak-worker.bundle.54d68bc1808ad3d77994.js → suv-peak-worker.bundle.48128a064ed28e623bf8.js} +1 -1
- package/dist/sw.js +1 -1
- package/package.json +20 -20
- package/dist/3482.css +0 -1
- /package/dist/{153.bundle.3772df1d5fd3d3f06a0a.js → 153.bundle.3727289b597308e3295f.js} +0 -0
- /package/dist/{2791.bundle.2f25931df62e9ebaafb2.js → 2791.bundle.7874c31472a55a7b35a7.js} +0 -0
- /package/dist/{4353.bundle.cf8a64e611cb6330f3e5.js → 4353.bundle.e7f048a0002dc3bb19fb.js} +0 -0
- /package/dist/{4978.bundle.16df43d1e89383235312.js → 4978.bundle.6474620f33562f3418f8.js} +0 -0
- /package/dist/{6591.bundle.78485be183f68b456495.js → 6591.bundle.1c29e66a86ab07463109.js} +0 -0
- /package/dist/{7246.bundle.b6c6f4324d62ab2960bb.js → 7246.bundle.cbc90a04abb3d771d7ba.js} +0 -0
- /package/dist/{7376.bundle.36672e2cbaafb7fa23b3.js → 7376.bundle.b31063489283cc2c5e16.js} +0 -0
- /package/dist/{7502.bundle.58965e833bce0661b63f.js → 7502.bundle.6f9c7545a4ed24158417.js} +0 -0
- /package/dist/{8944.bundle.8de6791692d3629c92a5.js → 8944.bundle.0cc6d2a5df7aa2de1c81.js} +0 -0
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[
|
|
2
|
+
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[4566],{
|
|
3
3
|
|
|
4
|
-
/***/
|
|
4
|
+
/***/ 44736:
|
|
5
5
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
6
6
|
|
|
7
7
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
8
|
-
/* harmony export */
|
|
8
|
+
/* harmony export */ Ql: () => (/* binding */ CodingSchemeDesignators),
|
|
9
|
+
/* harmony export */ ZP: () => (__WEBPACK_DEFAULT_EXPORT__),
|
|
10
|
+
/* harmony export */ oQ: () => (/* binding */ SCOORDTypes),
|
|
11
|
+
/* harmony export */ zg: () => (/* binding */ CodeNameCodeSequenceValues)
|
|
9
12
|
/* harmony export */ });
|
|
13
|
+
/* unused harmony export RelationshipType */
|
|
10
14
|
/* harmony import */ var _cornerstonejs_adapters__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(91202);
|
|
11
15
|
|
|
12
16
|
const {
|
|
13
17
|
CodeScheme: Cornerstone3DCodeScheme
|
|
14
18
|
} = _cornerstonejs_adapters__WEBPACK_IMPORTED_MODULE_0__.adaptersSR.Cornerstone3D;
|
|
19
|
+
const SCOORDTypes = {
|
|
20
|
+
POINT: 'POINT',
|
|
21
|
+
MULTIPOINT: 'MULTIPOINT',
|
|
22
|
+
POLYLINE: 'POLYLINE',
|
|
23
|
+
CIRCLE: 'CIRCLE',
|
|
24
|
+
ELLIPSE: 'ELLIPSE'
|
|
25
|
+
};
|
|
15
26
|
const CodeNameCodeSequenceValues = {
|
|
16
27
|
ImagingMeasurementReport: '126000',
|
|
17
28
|
ImageLibrary: '111028',
|
|
@@ -23,13 +34,28 @@ const CodeNameCodeSequenceValues = {
|
|
|
23
34
|
Finding: '121071',
|
|
24
35
|
FindingSite: 'G-C0E3',
|
|
25
36
|
// SRT
|
|
26
|
-
|
|
37
|
+
FindingSiteSCT: '363698007' // SCT
|
|
27
38
|
};
|
|
28
|
-
|
|
39
|
+
const CodingSchemeDesignators = {
|
|
40
|
+
SRT: 'SRT',
|
|
41
|
+
SCT: 'SCT',
|
|
42
|
+
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4']
|
|
43
|
+
};
|
|
44
|
+
const RelationshipType = {
|
|
45
|
+
INFERRED_FROM: 'INFERRED FROM',
|
|
46
|
+
CONTAINS: 'CONTAINS'
|
|
47
|
+
};
|
|
48
|
+
const enums = {
|
|
49
|
+
CodeNameCodeSequenceValues,
|
|
50
|
+
CodingSchemeDesignators,
|
|
51
|
+
RelationshipType,
|
|
52
|
+
SCOORDTypes
|
|
53
|
+
};
|
|
54
|
+
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (enums);
|
|
29
55
|
|
|
30
56
|
/***/ }),
|
|
31
57
|
|
|
32
|
-
/***/
|
|
58
|
+
/***/ 84566:
|
|
33
59
|
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
|
34
60
|
|
|
35
61
|
// ESM COMPAT FLAG
|
|
@@ -37,482 +63,250 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
37
63
|
|
|
38
64
|
// EXPORTS
|
|
39
65
|
__webpack_require__.d(__webpack_exports__, {
|
|
66
|
+
Enums: () => (/* reexport */ enums/* default */.ZP),
|
|
40
67
|
createReferencedImageDisplaySet: () => (/* reexport */ createReferencedImageDisplaySet/* default */.Z),
|
|
41
68
|
"default": () => (/* binding */ cornerstone_dicom_sr_src),
|
|
42
69
|
hydrateStructuredReport: () => (/* reexport */ hydrateStructuredReport/* default */.Z),
|
|
43
|
-
srProtocol: () => (/* reexport */ srProtocol)
|
|
70
|
+
srProtocol: () => (/* reexport */ srProtocol),
|
|
71
|
+
toolNames: () => (/* reexport */ tools_toolNames)
|
|
44
72
|
});
|
|
45
73
|
|
|
46
74
|
// EXTERNAL MODULE: ../../../node_modules/react/index.js
|
|
47
75
|
var react = __webpack_require__(43001);
|
|
48
|
-
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/package.json
|
|
49
|
-
const package_namespaceObject = JSON.parse('{"u2":"@ohif/extension-cornerstone-dicom-sr"}');
|
|
50
|
-
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/id.js
|
|
51
|
-
|
|
52
|
-
const id = package_namespaceObject.u2;
|
|
53
|
-
const SOPClassHandlerName = 'dicom-sr';
|
|
54
|
-
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
|
55
|
-
|
|
56
76
|
// EXTERNAL MODULE: ../../core/src/index.ts + 74 modules
|
|
57
77
|
var src = __webpack_require__(84793);
|
|
58
|
-
// EXTERNAL MODULE: ../../../
|
|
59
|
-
var
|
|
78
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone/src/index.tsx + 96 modules
|
|
79
|
+
var cornerstone_src = __webpack_require__(83846);
|
|
80
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/adapters.es.js
|
|
81
|
+
var adapters_es = __webpack_require__(91202);
|
|
60
82
|
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js
|
|
61
|
-
var
|
|
83
|
+
var esm = __webpack_require__(93725);
|
|
62
84
|
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 1 modules
|
|
63
|
-
var
|
|
64
|
-
// EXTERNAL MODULE: ../../../
|
|
65
|
-
var
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
MULTIPOINT: 'MULTIPOINT',
|
|
70
|
-
POLYLINE: 'POLYLINE',
|
|
71
|
-
CIRCLE: 'CIRCLE',
|
|
72
|
-
ELLIPSE: 'ELLIPSE'
|
|
73
|
-
});
|
|
74
|
-
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts
|
|
85
|
+
var dist_esm = __webpack_require__(12651);
|
|
86
|
+
// EXTERNAL MODULE: ../../../node_modules/gl-matrix/esm/index.js + 1 modules
|
|
87
|
+
var gl_matrix_esm = __webpack_require__(72076);
|
|
88
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone-dicom-sr/src/enums.ts
|
|
89
|
+
var enums = __webpack_require__(44736);
|
|
90
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/getRenderableData.ts
|
|
75
91
|
|
|
76
92
|
|
|
77
93
|
|
|
94
|
+
const EPSILON = 1e-4;
|
|
95
|
+
const getRenderableCoords = ({
|
|
96
|
+
GraphicData,
|
|
97
|
+
ValueType,
|
|
98
|
+
imageId
|
|
99
|
+
}) => {
|
|
100
|
+
const renderableData = [];
|
|
101
|
+
if (ValueType === 'SCOORD3D') {
|
|
102
|
+
for (let i = 0; i < GraphicData.length; i += 3) {
|
|
103
|
+
renderableData.push([GraphicData[i], GraphicData[i + 1], GraphicData[i + 2]]);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
107
|
+
const worldPos = dist_esm.utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
|
108
|
+
renderableData.push(worldPos);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return renderableData;
|
|
112
|
+
};
|
|
113
|
+
function getRenderableData({
|
|
114
|
+
GraphicType,
|
|
115
|
+
GraphicData,
|
|
116
|
+
ValueType,
|
|
117
|
+
imageId
|
|
118
|
+
}) {
|
|
119
|
+
let renderableData = [];
|
|
120
|
+
switch (GraphicType) {
|
|
121
|
+
case enums/* SCOORDTypes */.oQ.POINT:
|
|
122
|
+
case enums/* SCOORDTypes */.oQ.MULTIPOINT:
|
|
123
|
+
case enums/* SCOORDTypes */.oQ.POLYLINE:
|
|
124
|
+
{
|
|
125
|
+
renderableData = getRenderableCoords({
|
|
126
|
+
GraphicData,
|
|
127
|
+
ValueType,
|
|
128
|
+
imageId
|
|
129
|
+
});
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case enums/* SCOORDTypes */.oQ.CIRCLE:
|
|
133
|
+
{
|
|
134
|
+
const pointsWorld = getRenderableCoords({
|
|
135
|
+
GraphicData,
|
|
136
|
+
ValueType,
|
|
137
|
+
imageId
|
|
138
|
+
});
|
|
139
|
+
// We do not have an explicit draw circle svg helper in Cornerstone3D at
|
|
140
|
+
// this time, but we can use the ellipse svg helper to draw a circle, so
|
|
141
|
+
// here we reshape the data for that purpose.
|
|
142
|
+
const center = pointsWorld[0];
|
|
143
|
+
const onPerimeter = pointsWorld[1];
|
|
144
|
+
const radius = gl_matrix_esm/* vec3.distance */.R3.distance(center, onPerimeter);
|
|
145
|
+
const imagePlaneModule = dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
146
|
+
if (!imagePlaneModule) {
|
|
147
|
+
throw new Error('No imagePlaneModule found');
|
|
148
|
+
}
|
|
149
|
+
const {
|
|
150
|
+
columnCosines,
|
|
151
|
+
rowCosines
|
|
152
|
+
} = imagePlaneModule;
|
|
78
153
|
|
|
79
|
-
|
|
80
|
-
constructor(toolProps = {}, defaultToolProps = {
|
|
81
|
-
configuration: {}
|
|
82
|
-
}) {
|
|
83
|
-
super(toolProps, defaultToolProps);
|
|
84
|
-
// This tool should not inherit from AnnotationTool and we should not need
|
|
85
|
-
// to add the following lines.
|
|
86
|
-
this.isPointNearTool = () => null;
|
|
87
|
-
this.getHandleNearImagePoint = () => null;
|
|
88
|
-
this.renderAnnotation = (enabledElement, svgDrawingHelper) => {
|
|
89
|
-
const {
|
|
90
|
-
viewport
|
|
91
|
-
} = enabledElement;
|
|
92
|
-
const {
|
|
93
|
-
element
|
|
94
|
-
} = viewport;
|
|
95
|
-
let annotations = dist_esm.annotation.state.getAnnotations(this.getToolName(), element);
|
|
154
|
+
// we need to get major/minor axis (which are both the same size major = minor)
|
|
96
155
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
156
|
+
const firstAxisStart = gl_matrix_esm/* vec3.create */.R3.create();
|
|
157
|
+
gl_matrix_esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
|
|
158
|
+
const firstAxisEnd = gl_matrix_esm/* vec3.create */.R3.create();
|
|
159
|
+
gl_matrix_esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
|
|
160
|
+
const secondAxisStart = gl_matrix_esm/* vec3.create */.R3.create();
|
|
161
|
+
gl_matrix_esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
|
|
162
|
+
const secondAxisEnd = gl_matrix_esm/* vec3.create */.R3.create();
|
|
163
|
+
gl_matrix_esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(secondAxisEnd, center, rowCosines, -radius);
|
|
164
|
+
renderableData = [firstAxisStart, firstAxisEnd, secondAxisStart, secondAxisEnd];
|
|
165
|
+
break;
|
|
104
166
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
167
|
+
case enums/* SCOORDTypes */.oQ.ELLIPSE:
|
|
168
|
+
{
|
|
169
|
+
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
|
170
|
+
// But Cornerstone3D points are ordered as top, bottom, left, right for the
|
|
171
|
+
// ellipse so we need to identify if the majorAxis is horizontal or vertical
|
|
172
|
+
// and then choose the correct points to use for the ellipse.
|
|
173
|
+
const pointsWorld = getRenderableCoords({
|
|
174
|
+
GraphicData,
|
|
175
|
+
ValueType,
|
|
176
|
+
imageId
|
|
177
|
+
});
|
|
178
|
+
const majorAxisStart = gl_matrix_esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[0]);
|
|
179
|
+
const majorAxisEnd = gl_matrix_esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[1]);
|
|
180
|
+
const minorAxisStart = gl_matrix_esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[2]);
|
|
181
|
+
const minorAxisEnd = gl_matrix_esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[3]);
|
|
182
|
+
const majorAxisVec = gl_matrix_esm/* vec3.create */.R3.create();
|
|
183
|
+
gl_matrix_esm/* vec3.sub */.R3.sub(majorAxisVec, majorAxisEnd, majorAxisStart);
|
|
111
184
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
};
|
|
122
|
-
const {
|
|
123
|
-
style: annotationStyle
|
|
124
|
-
} = dist_esm.annotation.config;
|
|
125
|
-
for (let i = 0; i < filteredAnnotations.length; i++) {
|
|
126
|
-
const annotation = filteredAnnotations[i];
|
|
127
|
-
const annotationUID = annotation.annotationUID;
|
|
128
|
-
const {
|
|
129
|
-
renderableData,
|
|
130
|
-
TrackingUniqueIdentifier
|
|
131
|
-
} = annotation.data;
|
|
185
|
+
// normalize majorAxisVec to avoid scaling issues
|
|
186
|
+
gl_matrix_esm/* vec3.normalize */.R3.normalize(majorAxisVec, majorAxisVec);
|
|
187
|
+
const minorAxisVec = gl_matrix_esm/* vec3.create */.R3.create();
|
|
188
|
+
gl_matrix_esm/* vec3.sub */.R3.sub(minorAxisVec, minorAxisEnd, minorAxisStart);
|
|
189
|
+
gl_matrix_esm/* vec3.normalize */.R3.normalize(minorAxisVec, minorAxisVec);
|
|
190
|
+
const imagePlaneModule = dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
191
|
+
if (!imagePlaneModule) {
|
|
192
|
+
throw new Error('imageId does not have imagePlaneModule metadata');
|
|
193
|
+
}
|
|
132
194
|
const {
|
|
133
|
-
|
|
134
|
-
} =
|
|
135
|
-
styleSpecifier.annotationUID = annotationUID;
|
|
136
|
-
const groupStyle = annotationStyle.getToolGroupToolStyles(this.toolGroupId)[this.getToolName()];
|
|
137
|
-
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
|
138
|
-
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
|
139
|
-
const color = TrackingUniqueIdentifier === activeTrackingUniqueIdentifier ? 'rgb(0, 255, 0)' : this.getStyle('color', styleSpecifier, annotation);
|
|
140
|
-
const options = {
|
|
141
|
-
color,
|
|
142
|
-
lineDash,
|
|
143
|
-
lineWidth,
|
|
144
|
-
...groupStyle
|
|
145
|
-
};
|
|
146
|
-
Object.keys(renderableData).forEach(GraphicType => {
|
|
147
|
-
const renderableDataForGraphicType = renderableData[GraphicType];
|
|
148
|
-
let renderMethod;
|
|
149
|
-
let canvasCoordinatesAdapter;
|
|
150
|
-
switch (GraphicType) {
|
|
151
|
-
case scoordTypes.POINT:
|
|
152
|
-
renderMethod = this.renderPoint;
|
|
153
|
-
break;
|
|
154
|
-
case scoordTypes.MULTIPOINT:
|
|
155
|
-
renderMethod = this.renderMultipoint;
|
|
156
|
-
break;
|
|
157
|
-
case scoordTypes.POLYLINE:
|
|
158
|
-
renderMethod = this.renderPolyLine;
|
|
159
|
-
break;
|
|
160
|
-
case scoordTypes.CIRCLE:
|
|
161
|
-
renderMethod = this.renderEllipse;
|
|
162
|
-
break;
|
|
163
|
-
case scoordTypes.ELLIPSE:
|
|
164
|
-
renderMethod = this.renderEllipse;
|
|
165
|
-
canvasCoordinatesAdapter = dist_esm.utilities.math.ellipse.getCanvasEllipseCorners;
|
|
166
|
-
break;
|
|
167
|
-
default:
|
|
168
|
-
throw new Error(`Unsupported GraphicType: ${GraphicType}`);
|
|
169
|
-
}
|
|
170
|
-
const canvasCoordinates = renderMethod(svgDrawingHelper, viewport, renderableDataForGraphicType, annotationUID, referencedImageId, options);
|
|
171
|
-
this.renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, canvasCoordinatesAdapter, annotation, styleSpecifier, options);
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
_getTextBoxLinesFromLabels(labels) {
|
|
177
|
-
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!
|
|
195
|
+
columnCosines
|
|
196
|
+
} = imagePlaneModule;
|
|
178
197
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
renderableData.map((data, index) => {
|
|
195
|
-
const canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
|
196
|
-
const lineUID = `${index}`;
|
|
197
|
-
if (canvasCoordinates.length === 2) {
|
|
198
|
-
dist_esm.drawing.drawLine(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates[0], canvasCoordinates[1], drawingOptions);
|
|
199
|
-
} else {
|
|
200
|
-
dist_esm.drawing.drawPolyline(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates, drawingOptions);
|
|
198
|
+
// find which axis is parallel to the columnCosines
|
|
199
|
+
const columnCosinesVec = gl_matrix_esm/* vec3.fromValues */.R3.fromValues(...columnCosines);
|
|
200
|
+
const projectedMajorAxisOnColVec = Math.abs(gl_matrix_esm/* vec3.dot */.R3.dot(columnCosinesVec, majorAxisVec));
|
|
201
|
+
const projectedMinorAxisOnColVec = Math.abs(gl_matrix_esm/* vec3.dot */.R3.dot(columnCosinesVec, minorAxisVec));
|
|
202
|
+
const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec);
|
|
203
|
+
const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec);
|
|
204
|
+
renderableData = [];
|
|
205
|
+
if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) {
|
|
206
|
+
renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]];
|
|
207
|
+
} else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) {
|
|
208
|
+
renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]];
|
|
209
|
+
} else {
|
|
210
|
+
console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED');
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
201
213
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return allCanvasCoordinates; // used for drawing textBox
|
|
205
|
-
}
|
|
206
|
-
renderMultipoint(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
207
|
-
let canvasCoordinates;
|
|
208
|
-
renderableData.map((data, index) => {
|
|
209
|
-
canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
|
210
|
-
const handleGroupUID = '0';
|
|
211
|
-
dist_esm.drawing.drawHandles(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, {
|
|
212
|
-
color: options.color
|
|
213
|
-
});
|
|
214
|
-
});
|
|
214
|
+
default:
|
|
215
|
+
console.warn('Unsupported GraphicType:', GraphicType);
|
|
215
216
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
217
|
+
return renderableData;
|
|
218
|
+
}
|
|
219
|
+
/* harmony default export */ const utils_getRenderableData = (getRenderableData);
|
|
220
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/toolNames.ts
|
|
221
|
+
const toolNames = {
|
|
222
|
+
DICOMSRDisplay: 'DICOMSRDisplay',
|
|
223
|
+
SRLength: 'SRLength',
|
|
224
|
+
SRBidirectional: 'SRBidirectional',
|
|
225
|
+
SREllipticalROI: 'SREllipticalROI',
|
|
226
|
+
SRCircleROI: 'SRCircleROI',
|
|
227
|
+
SRArrowAnnotate: 'SRArrowAnnotate',
|
|
228
|
+
SRAngle: 'SRAngle',
|
|
229
|
+
SRCobbAngle: 'SRCobbAngle',
|
|
230
|
+
SRRectangleROI: 'SRRectangleROI',
|
|
231
|
+
SRPlanarFreehandROI: 'SRPlanarFreehandROI',
|
|
232
|
+
SRSCOORD3DPoint: 'SRSCOORD3DPoint'
|
|
233
|
+
};
|
|
234
|
+
/* harmony default export */ const tools_toolNames = (toolNames);
|
|
235
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/addSRAnnotation.ts
|
|
222
236
|
|
|
223
|
-
// We get the other point for the arrow by using the image size
|
|
224
|
-
const imagePixelModule = core_dist_esm.metaData.get('imagePixelModule', referencedImageId);
|
|
225
|
-
let xOffset = 10;
|
|
226
|
-
let yOffset = 10;
|
|
227
|
-
if (imagePixelModule) {
|
|
228
|
-
const {
|
|
229
|
-
columns,
|
|
230
|
-
rows
|
|
231
|
-
} = imagePixelModule;
|
|
232
|
-
xOffset = columns / 10;
|
|
233
|
-
yOffset = rows / 10;
|
|
234
|
-
}
|
|
235
|
-
const imagePoint = core_dist_esm.utilities.worldToImageCoords(referencedImageId, point);
|
|
236
|
-
const arrowEnd = core_dist_esm.utilities.imageToWorldCoords(referencedImageId, [imagePoint[0] + xOffset, imagePoint[1] + yOffset]);
|
|
237
|
-
canvasCoordinates.push(viewport.worldToCanvas(arrowEnd));
|
|
238
|
-
const arrowUID = `${index}`;
|
|
239
237
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
function addSRAnnotation(measurement, imageId, frameNumber) {
|
|
241
|
+
let toolName = tools_toolNames.DICOMSRDisplay;
|
|
242
|
+
const renderableData = measurement.coords.reduce((acc, coordProps) => {
|
|
243
|
+
acc[coordProps.GraphicType] = acc[coordProps.GraphicType] || [];
|
|
244
|
+
acc[coordProps.GraphicType].push(utils_getRenderableData({
|
|
245
|
+
...coordProps,
|
|
246
|
+
imageId
|
|
247
|
+
}));
|
|
248
|
+
return acc;
|
|
249
|
+
}, {});
|
|
250
|
+
const {
|
|
251
|
+
TrackingUniqueIdentifier
|
|
252
|
+
} = measurement;
|
|
253
|
+
const {
|
|
254
|
+
ValueType: valueType,
|
|
255
|
+
GraphicType: graphicType
|
|
256
|
+
} = measurement.coords[0];
|
|
257
|
+
const graphicTypePoints = renderableData[graphicType];
|
|
258
|
+
|
|
259
|
+
/** TODO: Read the tool name from the DICOM SR identification type in the future. */
|
|
260
|
+
let frameOfReferenceUID = null;
|
|
261
|
+
if (imageId) {
|
|
262
|
+
const imagePlaneModule = dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
263
|
+
frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID;
|
|
247
264
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
renderableData.map((data, index) => {
|
|
251
|
-
if (data.length === 0) {
|
|
252
|
-
// since oblique ellipse is not supported for hydration right now
|
|
253
|
-
// we just return
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
const ellipsePointsWorld = data;
|
|
257
|
-
const rotation = viewport.getRotation();
|
|
258
|
-
canvasCoordinates = ellipsePointsWorld.map(p => viewport.worldToCanvas(p));
|
|
259
|
-
let canvasCorners;
|
|
260
|
-
if (rotation == 90 || rotation == 270) {
|
|
261
|
-
canvasCorners = dist_esm.utilities.math.ellipse.getCanvasEllipseCorners([canvasCoordinates[2], canvasCoordinates[3], canvasCoordinates[0], canvasCoordinates[1]]);
|
|
262
|
-
} else {
|
|
263
|
-
canvasCorners = dist_esm.utilities.math.ellipse.getCanvasEllipseCorners(canvasCoordinates);
|
|
264
|
-
}
|
|
265
|
-
const lineUID = `${index}`;
|
|
266
|
-
dist_esm.drawing.drawEllipse(svgDrawingHelper, annotationUID, lineUID, canvasCorners[0], canvasCorners[1], {
|
|
267
|
-
color: options.color,
|
|
268
|
-
width: options.lineWidth,
|
|
269
|
-
lineDash: options.lineDash
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
return canvasCoordinates;
|
|
273
|
-
}
|
|
274
|
-
renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, canvasCoordinatesAdapter, annotation, styleSpecifier, options = {}) {
|
|
275
|
-
if (!canvasCoordinates || !annotation) {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const {
|
|
279
|
-
annotationUID,
|
|
280
|
-
data = {}
|
|
281
|
-
} = annotation;
|
|
282
|
-
const {
|
|
283
|
-
label
|
|
284
|
-
} = data;
|
|
285
|
-
const {
|
|
286
|
-
color
|
|
287
|
-
} = options;
|
|
288
|
-
let adaptedCanvasCoordinates = canvasCoordinates;
|
|
289
|
-
// adapt coordinates if there is an adapter
|
|
290
|
-
if (typeof canvasCoordinatesAdapter === 'function') {
|
|
291
|
-
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
|
292
|
-
}
|
|
293
|
-
const textLines = this._getTextBoxLinesFromLabels(label);
|
|
294
|
-
const canvasTextBoxCoords = dist_esm.utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
|
295
|
-
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
|
296
|
-
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
|
297
|
-
}
|
|
298
|
-
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
|
299
|
-
const textBoxUID = '1';
|
|
300
|
-
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
|
301
|
-
const boundingBox = dist_esm.drawing.drawLinkedTextBox(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, canvasCoordinates, {}, {
|
|
302
|
-
...textBoxOptions,
|
|
303
|
-
color
|
|
304
|
-
});
|
|
305
|
-
const {
|
|
306
|
-
x: left,
|
|
307
|
-
y: top,
|
|
308
|
-
width,
|
|
309
|
-
height
|
|
310
|
-
} = boundingBox;
|
|
311
|
-
annotation.data.handles.textBox.worldBoundingBox = {
|
|
312
|
-
topLeft: viewport.canvasToWorld([left, top]),
|
|
313
|
-
topRight: viewport.canvasToWorld([left + width, top]),
|
|
314
|
-
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
315
|
-
bottomRight: viewport.canvasToWorld([left + width, top + height])
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
DICOMSRDisplayTool.toolName = 'DICOMSRDisplay';
|
|
320
|
-
const SHORT_HAND_MAP = {
|
|
321
|
-
'Short Axis': 'W: ',
|
|
322
|
-
'Long Axis': 'L: ',
|
|
323
|
-
AREA: 'Area: ',
|
|
324
|
-
Length: '',
|
|
325
|
-
CORNERSTONEFREETEXT: ''
|
|
326
|
-
};
|
|
327
|
-
function _labelToShorthand(label) {
|
|
328
|
-
const shortHand = SHORT_HAND_MAP[label];
|
|
329
|
-
if (shortHand !== undefined) {
|
|
330
|
-
return shortHand;
|
|
331
|
-
}
|
|
332
|
-
return label;
|
|
333
|
-
}
|
|
334
|
-
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/toolNames.ts
|
|
335
|
-
|
|
336
|
-
const toolNames = {
|
|
337
|
-
DICOMSRDisplay: DICOMSRDisplayTool.toolName,
|
|
338
|
-
SRLength: 'SRLength',
|
|
339
|
-
SRBidirectional: 'SRBidirectional',
|
|
340
|
-
SREllipticalROI: 'SREllipticalROI',
|
|
341
|
-
SRCircleROI: 'SRCircleROI',
|
|
342
|
-
SRArrowAnnotate: 'SRArrowAnnotate',
|
|
343
|
-
SRAngle: 'SRAngle',
|
|
344
|
-
SRCobbAngle: 'SRCobbAngle',
|
|
345
|
-
SRRectangleROI: 'SRRectangleROI',
|
|
346
|
-
SRPlanarFreehandROI: 'SRPlanarFreehandROI'
|
|
347
|
-
};
|
|
348
|
-
/* harmony default export */ const tools_toolNames = (toolNames);
|
|
349
|
-
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/addDICOMSRDisplayAnnotation.ts
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const EPSILON = 1e-4;
|
|
356
|
-
function addDICOMSRDisplayAnnotation(measurement, imageId, frameNumber) {
|
|
357
|
-
const toolName = tools_toolNames.DICOMSRDisplay;
|
|
358
|
-
const measurementData = {
|
|
359
|
-
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
|
360
|
-
renderableData: {},
|
|
361
|
-
labels: measurement.labels,
|
|
362
|
-
imageId
|
|
363
|
-
};
|
|
364
|
-
measurement.coords.forEach(coord => {
|
|
365
|
-
const {
|
|
366
|
-
GraphicType,
|
|
367
|
-
GraphicData
|
|
368
|
-
} = coord;
|
|
369
|
-
if (measurementData.renderableData[GraphicType] === undefined) {
|
|
370
|
-
measurementData.renderableData[GraphicType] = [];
|
|
371
|
-
}
|
|
372
|
-
measurementData.renderableData[GraphicType].push(_getRenderableData(GraphicType, GraphicData, imageId));
|
|
373
|
-
});
|
|
374
|
-
const imagePlaneModule = core_dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
265
|
+
if (valueType === 'SCOORD3D') {
|
|
266
|
+
toolName = tools_toolNames.SRSCOORD3DPoint;
|
|
375
267
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
* to the measurement service and will be available for the other viewports.
|
|
380
|
-
*/
|
|
268
|
+
// get the ReferencedFrameOfReferenceUID from the measurement
|
|
269
|
+
frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence;
|
|
270
|
+
}
|
|
381
271
|
const SRAnnotation = {
|
|
382
|
-
annotationUID:
|
|
272
|
+
annotationUID: TrackingUniqueIdentifier,
|
|
383
273
|
highlighted: false,
|
|
384
274
|
isLocked: false,
|
|
385
275
|
invalidated: false,
|
|
386
276
|
metadata: {
|
|
387
|
-
toolName
|
|
388
|
-
|
|
277
|
+
toolName,
|
|
278
|
+
valueType,
|
|
279
|
+
graphicType,
|
|
280
|
+
FrameOfReferenceUID: frameOfReferenceUID,
|
|
389
281
|
referencedImageId: imageId
|
|
390
282
|
},
|
|
391
283
|
data: {
|
|
392
|
-
label: measurement.labels,
|
|
284
|
+
label: measurement.labels?.[0]?.value || undefined,
|
|
285
|
+
displayText: measurement.displayText || undefined,
|
|
393
286
|
handles: {
|
|
394
|
-
textBox: measurement.textBox ?? {}
|
|
287
|
+
textBox: measurement.textBox ?? {},
|
|
288
|
+
points: graphicTypePoints[0]
|
|
395
289
|
},
|
|
396
290
|
cachedStats: {},
|
|
397
|
-
|
|
398
|
-
renderableData
|
|
399
|
-
|
|
291
|
+
frameNumber,
|
|
292
|
+
renderableData,
|
|
293
|
+
TrackingUniqueIdentifier,
|
|
294
|
+
labels: measurement.labels
|
|
400
295
|
}
|
|
401
296
|
};
|
|
402
|
-
const annotationManager = dist_esm.annotation.state.getAnnotationManager();
|
|
403
|
-
annotationManager.addAnnotation(SRAnnotation);
|
|
404
|
-
}
|
|
405
|
-
function _getRenderableData(GraphicType, GraphicData, imageId) {
|
|
406
|
-
let renderableData;
|
|
407
|
-
switch (GraphicType) {
|
|
408
|
-
case scoordTypes.POINT:
|
|
409
|
-
case scoordTypes.MULTIPOINT:
|
|
410
|
-
case scoordTypes.POLYLINE:
|
|
411
|
-
renderableData = [];
|
|
412
|
-
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
413
|
-
const worldPos = core_dist_esm.utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
|
414
|
-
renderableData.push(worldPos);
|
|
415
|
-
}
|
|
416
|
-
break;
|
|
417
|
-
case scoordTypes.CIRCLE:
|
|
418
|
-
{
|
|
419
|
-
const pointsWorld = [];
|
|
420
|
-
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
421
|
-
const worldPos = core_dist_esm.utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
|
422
|
-
pointsWorld.push(worldPos);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// We do not have an explicit draw circle svg helper in Cornerstone3D at
|
|
426
|
-
// this time, but we can use the ellipse svg helper to draw a circle, so
|
|
427
|
-
// here we reshape the data for that purpose.
|
|
428
|
-
const center = pointsWorld[0];
|
|
429
|
-
const onPerimeter = pointsWorld[1];
|
|
430
|
-
const radius = esm/* vec3.distance */.R3.distance(center, onPerimeter);
|
|
431
|
-
const imagePlaneModule = core_dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
432
|
-
if (!imagePlaneModule) {
|
|
433
|
-
throw new Error('No imagePlaneModule found');
|
|
434
|
-
}
|
|
435
|
-
const {
|
|
436
|
-
columnCosines,
|
|
437
|
-
rowCosines
|
|
438
|
-
} = imagePlaneModule;
|
|
439
|
-
|
|
440
|
-
// we need to get major/minor axis (which are both the same size major = minor)
|
|
441
|
-
|
|
442
|
-
// first axisStart
|
|
443
|
-
const firstAxisStart = esm/* vec3.create */.R3.create();
|
|
444
|
-
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
|
|
445
|
-
const firstAxisEnd = esm/* vec3.create */.R3.create();
|
|
446
|
-
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
|
|
447
|
-
|
|
448
|
-
// second axisStart
|
|
449
|
-
const secondAxisStart = esm/* vec3.create */.R3.create();
|
|
450
|
-
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
|
|
451
|
-
const secondAxisEnd = esm/* vec3.create */.R3.create();
|
|
452
|
-
esm/* vec3.scaleAndAdd */.R3.scaleAndAdd(secondAxisEnd, center, rowCosines, -radius);
|
|
453
|
-
renderableData = [firstAxisStart, firstAxisEnd, secondAxisStart, secondAxisEnd];
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
case scoordTypes.ELLIPSE:
|
|
457
|
-
{
|
|
458
|
-
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
|
459
|
-
// But Cornerstone3D points are ordered as top, bottom, left, right for the
|
|
460
|
-
// ellipse so we need to identify if the majorAxis is horizontal or vertical
|
|
461
|
-
// and then choose the correct points to use for the ellipse.
|
|
462
|
-
|
|
463
|
-
const pointsWorld = [];
|
|
464
|
-
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
465
|
-
const worldPos = core_dist_esm.utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
|
466
|
-
pointsWorld.push(worldPos);
|
|
467
|
-
}
|
|
468
|
-
const majorAxisStart = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[0]);
|
|
469
|
-
const majorAxisEnd = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[1]);
|
|
470
|
-
const minorAxisStart = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[2]);
|
|
471
|
-
const minorAxisEnd = esm/* vec3.fromValues */.R3.fromValues(...pointsWorld[3]);
|
|
472
|
-
const majorAxisVec = esm/* vec3.create */.R3.create();
|
|
473
|
-
esm/* vec3.sub */.R3.sub(majorAxisVec, majorAxisEnd, majorAxisStart);
|
|
474
|
-
|
|
475
|
-
// normalize majorAxisVec to avoid scaling issues
|
|
476
|
-
esm/* vec3.normalize */.R3.normalize(majorAxisVec, majorAxisVec);
|
|
477
|
-
const minorAxisVec = esm/* vec3.create */.R3.create();
|
|
478
|
-
esm/* vec3.sub */.R3.sub(minorAxisVec, minorAxisEnd, minorAxisStart);
|
|
479
|
-
esm/* vec3.normalize */.R3.normalize(minorAxisVec, minorAxisVec);
|
|
480
|
-
const imagePlaneModule = core_dist_esm.metaData.get('imagePlaneModule', imageId);
|
|
481
|
-
if (!imagePlaneModule) {
|
|
482
|
-
throw new Error('imageId does not have imagePlaneModule metadata');
|
|
483
|
-
}
|
|
484
|
-
const {
|
|
485
|
-
columnCosines
|
|
486
|
-
} = imagePlaneModule;
|
|
487
297
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
renderableData = [];
|
|
495
|
-
if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) {
|
|
496
|
-
renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]];
|
|
497
|
-
} else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) {
|
|
498
|
-
renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]];
|
|
499
|
-
} else {
|
|
500
|
-
console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED');
|
|
501
|
-
}
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
default:
|
|
505
|
-
console.warn('Unsupported GraphicType:', GraphicType);
|
|
506
|
-
}
|
|
507
|
-
return renderableData;
|
|
298
|
+
/**
|
|
299
|
+
* const annotationManager = annotation.annotationState.getAnnotationManager();
|
|
300
|
+
* was not triggering annotation_added events.
|
|
301
|
+
*/
|
|
302
|
+
esm.annotation.state.addAnnotation(SRAnnotation);
|
|
303
|
+
console.debug('Adding SR annotation:', SRAnnotation);
|
|
508
304
|
}
|
|
509
|
-
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/adapters.es.js
|
|
510
|
-
var adapters_es = __webpack_require__(91202);
|
|
511
305
|
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js
|
|
512
306
|
|
|
513
307
|
const cornerstoneAdapters = adapters_es.adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
|
514
308
|
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
|
515
|
-
const CORNERSTONE_3D_TAG =
|
|
309
|
+
const CORNERSTONE_3D_TAG = adapters_es.adaptersSR.Cornerstone3D.CORNERSTONE_3D_TAG;
|
|
516
310
|
|
|
517
311
|
/**
|
|
518
312
|
* Checks if the given `displaySet`can be rehydrated into the `measurementService`.
|
|
@@ -558,8 +352,16 @@ function isRehydratable(displaySet, mappings) {
|
|
|
558
352
|
console.log('No measurements found which were rehydratable');
|
|
559
353
|
return false;
|
|
560
354
|
}
|
|
561
|
-
|
|
562
|
-
|
|
355
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/package.json
|
|
356
|
+
const package_namespaceObject = JSON.parse('{"u2":"@ohif/extension-cornerstone-dicom-sr"}');
|
|
357
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/id.js
|
|
358
|
+
|
|
359
|
+
const id = package_namespaceObject.u2;
|
|
360
|
+
const SOPClassHandlerName = 'dicom-sr';
|
|
361
|
+
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
|
362
|
+
const SOPClassHandlerName3D = 'dicom-sr-3d';
|
|
363
|
+
const SOPClassHandlerId3D = `${id}.sopClassHandlerModule.${SOPClassHandlerName3D}`;
|
|
364
|
+
|
|
563
365
|
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts
|
|
564
366
|
|
|
565
367
|
|
|
@@ -567,30 +369,29 @@ var CodeNameCodeSequenceValues = __webpack_require__(91851);
|
|
|
567
369
|
|
|
568
370
|
|
|
569
371
|
|
|
372
|
+
|
|
570
373
|
const {
|
|
571
|
-
|
|
572
|
-
} =
|
|
374
|
+
sopClassDictionary
|
|
375
|
+
} = src.utils;
|
|
376
|
+
const {
|
|
377
|
+
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
|
378
|
+
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
|
379
|
+
} = cornerstone_src.Enums;
|
|
573
380
|
const {
|
|
574
381
|
ImageSet,
|
|
575
382
|
MetadataProvider: metadataProvider
|
|
576
383
|
} = src.classes;
|
|
384
|
+
const {
|
|
385
|
+
CodeScheme: Cornerstone3DCodeScheme
|
|
386
|
+
} = adapters_es.adaptersSR.Cornerstone3D;
|
|
387
|
+
/**
|
|
388
|
+
* TODO
|
|
389
|
+
* - [ ] Add SR thumbnail
|
|
390
|
+
* - [ ] Make viewport
|
|
391
|
+
* - [ ] Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport
|
|
392
|
+
*/
|
|
577
393
|
|
|
578
|
-
|
|
579
|
-
// Add SR thumbnail
|
|
580
|
-
// Make viewport
|
|
581
|
-
// Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport.
|
|
582
|
-
|
|
583
|
-
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.88.11',
|
|
584
|
-
// BASIC TEXT SR
|
|
585
|
-
'1.2.840.10008.5.1.4.1.1.88.22',
|
|
586
|
-
// ENHANCED SR
|
|
587
|
-
'1.2.840.10008.5.1.4.1.1.88.33',
|
|
588
|
-
// COMPREHENSIVE SR
|
|
589
|
-
'1.2.840.10008.5.1.4.1.1.88.34' // Comprehensive 3D SR
|
|
590
|
-
// '1.2.840.10008.5.1.4.1.1.88.50', // Mammography CAD SR
|
|
591
|
-
];
|
|
592
|
-
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
|
593
|
-
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
|
394
|
+
const sopClassUids = [sopClassDictionary.BasicTextSR, sopClassDictionary.EnhancedSR, sopClassDictionary.ComprehensiveSR];
|
|
594
395
|
const validateSameStudyUID = (uid, instances) => {
|
|
595
396
|
instances.forEach(it => {
|
|
596
397
|
if (it.StudyInstanceUID !== uid) {
|
|
@@ -599,15 +400,6 @@ const validateSameStudyUID = (uid, instances) => {
|
|
|
599
400
|
}
|
|
600
401
|
});
|
|
601
402
|
};
|
|
602
|
-
const CodingSchemeDesignators = {
|
|
603
|
-
SRT: 'SRT',
|
|
604
|
-
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4']
|
|
605
|
-
};
|
|
606
|
-
const RELATIONSHIP_TYPE = {
|
|
607
|
-
INFERRED_FROM: 'INFERRED FROM',
|
|
608
|
-
CONTAINS: 'CONTAINS'
|
|
609
|
-
};
|
|
610
|
-
const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
|
|
611
403
|
|
|
612
404
|
/**
|
|
613
405
|
* Adds instances to the DICOM SR series, rather than creating a new
|
|
@@ -656,7 +448,8 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager)
|
|
|
656
448
|
SOPClassUID
|
|
657
449
|
} = instance;
|
|
658
450
|
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
|
659
|
-
const
|
|
451
|
+
const is3DSR = SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
|
452
|
+
const isImagingMeasurementReport = ConceptNameCodeSequence?.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.ImagingMeasurementReport;
|
|
660
453
|
const displaySet = {
|
|
661
454
|
Modality: 'SR',
|
|
662
455
|
displaySetInstanceUID: src.utils.guid(),
|
|
@@ -666,7 +459,7 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager)
|
|
|
666
459
|
SOPInstanceUID,
|
|
667
460
|
SeriesInstanceUID,
|
|
668
461
|
StudyInstanceUID,
|
|
669
|
-
SOPClassHandlerId: SOPClassHandlerId,
|
|
462
|
+
SOPClassHandlerId: is3DSR ? SOPClassHandlerId3D : SOPClassHandlerId,
|
|
670
463
|
SOPClassUID,
|
|
671
464
|
instances,
|
|
672
465
|
referencedImages: null,
|
|
@@ -681,7 +474,14 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager)
|
|
|
681
474
|
displaySet.load = () => _load(displaySet, servicesManager, extensionManager);
|
|
682
475
|
return [displaySet];
|
|
683
476
|
}
|
|
684
|
-
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Loads the display set with the given services and extension manager.
|
|
480
|
+
* @param srDisplaySet - The display set to load.
|
|
481
|
+
* @param servicesManager - The services manager containing displaySetService and measurementService.
|
|
482
|
+
* @param extensionManager - The extension manager containing data sources.
|
|
483
|
+
*/
|
|
484
|
+
async function _load(srDisplaySet, servicesManager, extensionManager) {
|
|
685
485
|
const {
|
|
686
486
|
displaySetService,
|
|
687
487
|
measurementService
|
|
@@ -690,7 +490,7 @@ async function _load(displaySet, servicesManager, extensionManager) {
|
|
|
690
490
|
const dataSource = dataSources[0];
|
|
691
491
|
const {
|
|
692
492
|
ContentSequence
|
|
693
|
-
} =
|
|
493
|
+
} = srDisplaySet.instance;
|
|
694
494
|
async function retrieveBulkData(obj, parentObj = null, key = null) {
|
|
695
495
|
for (const prop in obj) {
|
|
696
496
|
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
|
|
@@ -700,9 +500,9 @@ async function _load(displaySet, servicesManager, extensionManager) {
|
|
|
700
500
|
} else if (prop === 'BulkDataURI') {
|
|
701
501
|
const value = await dataSource.retrieve.bulkDataURI({
|
|
702
502
|
BulkDataURI: obj[prop],
|
|
703
|
-
StudyInstanceUID:
|
|
704
|
-
SeriesInstanceUID:
|
|
705
|
-
SOPInstanceUID:
|
|
503
|
+
StudyInstanceUID: srDisplaySet.instance.StudyInstanceUID,
|
|
504
|
+
SeriesInstanceUID: srDisplaySet.instance.SeriesInstanceUID,
|
|
505
|
+
SOPInstanceUID: srDisplaySet.instance.SOPInstanceUID
|
|
706
506
|
});
|
|
707
507
|
if (parentObj && key) {
|
|
708
508
|
parentObj[key] = new Float32Array(value);
|
|
@@ -710,123 +510,134 @@ async function _load(displaySet, servicesManager, extensionManager) {
|
|
|
710
510
|
}
|
|
711
511
|
}
|
|
712
512
|
}
|
|
713
|
-
if (
|
|
513
|
+
if (srDisplaySet.isLoaded !== true) {
|
|
714
514
|
await retrieveBulkData(ContentSequence);
|
|
715
515
|
}
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
|
|
516
|
+
if (srDisplaySet.isImagingMeasurementReport) {
|
|
517
|
+
srDisplaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
|
518
|
+
srDisplaySet.measurements = _getMeasurements(ContentSequence);
|
|
719
519
|
} else {
|
|
720
|
-
|
|
721
|
-
|
|
520
|
+
srDisplaySet.referencedImages = [];
|
|
521
|
+
srDisplaySet.measurements = [];
|
|
722
522
|
}
|
|
723
523
|
const mappings = measurementService.getSourceMappings(CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION);
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
524
|
+
srDisplaySet.isHydrated = false;
|
|
525
|
+
srDisplaySet.isRehydratable = isRehydratable(srDisplaySet, mappings);
|
|
526
|
+
srDisplaySet.isLoaded = true;
|
|
727
527
|
|
|
728
|
-
|
|
528
|
+
/** Check currently added displaySets and add measurements if the sources exist */
|
|
729
529
|
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
|
730
|
-
_checkIfCanAddMeasurementsToDisplaySet(
|
|
530
|
+
_checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, activeDisplaySet, dataSource, servicesManager);
|
|
731
531
|
});
|
|
732
532
|
|
|
733
|
-
|
|
533
|
+
/** Subscribe to new displaySets as the source may come in after */
|
|
734
534
|
displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => {
|
|
735
535
|
const {
|
|
736
536
|
displaySetsAdded
|
|
737
537
|
} = data;
|
|
738
|
-
|
|
739
|
-
|
|
538
|
+
/**
|
|
539
|
+
* If there are still some measurements that have not yet been loaded into cornerstone,
|
|
540
|
+
* See if we can load them onto any of the new displaySets.
|
|
541
|
+
*/
|
|
740
542
|
displaySetsAdded.forEach(newDisplaySet => {
|
|
741
|
-
_checkIfCanAddMeasurementsToDisplaySet(
|
|
543
|
+
_checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource, servicesManager);
|
|
742
544
|
});
|
|
743
545
|
});
|
|
744
546
|
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Checks if measurements can be added to a display set.
|
|
550
|
+
*
|
|
551
|
+
* @param srDisplaySet - The source display set containing measurements.
|
|
552
|
+
* @param newDisplaySet - The new display set to check if measurements can be added.
|
|
553
|
+
* @param dataSource - The data source used to retrieve image IDs.
|
|
554
|
+
* @param servicesManager - The services manager.
|
|
555
|
+
*/
|
|
745
556
|
function _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, newDisplaySet, dataSource, servicesManager) {
|
|
746
557
|
const {
|
|
747
558
|
customizationService
|
|
748
559
|
} = servicesManager.services;
|
|
749
|
-
|
|
750
|
-
if (unloadedMeasurements.length === 0) {
|
|
751
|
-
// All already loaded!
|
|
560
|
+
const unloadedMeasurements = srDisplaySet.measurements.filter(measurement => measurement.loaded === false);
|
|
561
|
+
if (unloadedMeasurements.length === 0 || !(newDisplaySet instanceof ImageSet) || newDisplaySet.unsupported) {
|
|
752
562
|
return;
|
|
753
563
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
564
|
+
|
|
565
|
+
// const { sopClassUids } = newDisplaySet;
|
|
566
|
+
// Create a Set for faster lookups
|
|
567
|
+
// const sopClassUidSet = new Set(sopClassUids);
|
|
568
|
+
|
|
569
|
+
// Create a Map to efficiently look up ImageIds by SOPInstanceUID and frame number
|
|
570
|
+
const imageIdMap = new Map();
|
|
571
|
+
const imageIds = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
|
572
|
+
for (const imageId of imageIds) {
|
|
573
|
+
const {
|
|
574
|
+
SOPInstanceUID,
|
|
575
|
+
frameNumber
|
|
576
|
+
} = metadataProvider.getUIDsFromImageID(imageId);
|
|
577
|
+
const key = `${SOPInstanceUID}:${frameNumber || 1}`;
|
|
578
|
+
imageIdMap.set(key, imageId);
|
|
757
579
|
}
|
|
758
|
-
if (
|
|
580
|
+
if (!unloadedMeasurements?.length) {
|
|
759
581
|
return;
|
|
760
582
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
583
|
+
const is3DSR = srDisplaySet.SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
|
584
|
+
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
|
585
|
+
let measurement = unloadedMeasurements[j];
|
|
586
|
+
const onBeforeSRAddMeasurement = customizationService.getModeCustomization('onBeforeSRAddMeasurement')?.value;
|
|
587
|
+
if (typeof onBeforeSRAddMeasurement === 'function') {
|
|
588
|
+
measurement = onBeforeSRAddMeasurement({
|
|
589
|
+
measurement,
|
|
590
|
+
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
|
591
|
+
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID
|
|
592
|
+
});
|
|
593
|
+
}
|
|
764
594
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
coords
|
|
775
|
-
} = measurement;
|
|
776
|
-
coords.forEach(coord => {
|
|
777
|
-
const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
|
778
|
-
if (!SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
|
779
|
-
SOPInstanceUIDs.push(SOPInstanceUID);
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
});
|
|
783
|
-
const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
|
784
|
-
for (const imageId of imageIdsForDisplaySet) {
|
|
785
|
-
if (!unloadedMeasurements.length) {
|
|
786
|
-
// All measurements loaded.
|
|
787
|
-
return;
|
|
595
|
+
// if it is 3d SR we can just add the SR annotation
|
|
596
|
+
if (is3DSR) {
|
|
597
|
+
addSRAnnotation(measurement, null, null);
|
|
598
|
+
measurement.loaded = true;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const referencedSOPSequence = measurement.coords[0].ReferencedSOPSequence;
|
|
602
|
+
if (!referencedSOPSequence) {
|
|
603
|
+
continue;
|
|
788
604
|
}
|
|
789
605
|
const {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const frame = measurement.coords[0].ReferencedSOPSequence && measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber || 1;
|
|
806
|
-
|
|
807
|
-
/** Add DICOMSRDisplay annotation for the SR viewport (only) */
|
|
808
|
-
addDICOMSRDisplayAnnotation(measurement, imageId, frame);
|
|
809
|
-
|
|
810
|
-
/** Update measurement properties */
|
|
811
|
-
measurement.loaded = true;
|
|
812
|
-
measurement.imageId = imageId;
|
|
813
|
-
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
|
814
|
-
measurement.ReferencedSOPInstanceUID = measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
|
815
|
-
measurement.frameNumber = frame;
|
|
816
|
-
delete measurement.coords;
|
|
817
|
-
unloadedMeasurements.splice(j, 1);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
606
|
+
ReferencedSOPInstanceUID
|
|
607
|
+
} = referencedSOPSequence;
|
|
608
|
+
const frame = referencedSOPSequence.ReferencedFrameNumber || 1;
|
|
609
|
+
const key = `${ReferencedSOPInstanceUID}:${frame}`;
|
|
610
|
+
const imageId = imageIdMap.get(key);
|
|
611
|
+
if (imageId && _measurementReferencesSOPInstanceUID(measurement, ReferencedSOPInstanceUID, frame)) {
|
|
612
|
+
addSRAnnotation(measurement, imageId, frame);
|
|
613
|
+
|
|
614
|
+
// Update measurement properties
|
|
615
|
+
measurement.loaded = true;
|
|
616
|
+
measurement.imageId = imageId;
|
|
617
|
+
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
|
618
|
+
measurement.ReferencedSOPInstanceUID = ReferencedSOPInstanceUID;
|
|
619
|
+
measurement.frameNumber = frame;
|
|
620
|
+
unloadedMeasurements.splice(j, 1);
|
|
820
621
|
}
|
|
821
622
|
}
|
|
822
623
|
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Checks if a measurement references a specific SOP Instance UID.
|
|
627
|
+
* @param measurement - The measurement object.
|
|
628
|
+
* @param SOPInstanceUID - The SOP Instance UID to check against.
|
|
629
|
+
* @param frameNumber - The frame number to check against (optional).
|
|
630
|
+
* @returns True if the measurement references the specified SOP Instance UID, false otherwise.
|
|
631
|
+
*/
|
|
823
632
|
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
|
824
633
|
const {
|
|
825
634
|
coords
|
|
826
635
|
} = measurement;
|
|
827
636
|
|
|
828
|
-
|
|
829
|
-
|
|
637
|
+
/**
|
|
638
|
+
* NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
|
639
|
+
* Standard. But for now, we will support only one ReferenceFrameNumber.
|
|
640
|
+
*/
|
|
830
641
|
const ReferencedFrameNumber = measurement.coords[0].ReferencedSOPSequence && measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber || 1;
|
|
831
642
|
if (frameNumber && Number(frameNumber) !== Number(ReferencedFrameNumber)) {
|
|
832
643
|
return false;
|
|
@@ -840,7 +651,17 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
|
|
840
651
|
return true;
|
|
841
652
|
}
|
|
842
653
|
}
|
|
654
|
+
return false;
|
|
843
655
|
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Retrieves the SOP class handler module.
|
|
659
|
+
*
|
|
660
|
+
* @param {Object} options - The options for retrieving the SOP class handler module.
|
|
661
|
+
* @param {Object} options.servicesManager - The services manager.
|
|
662
|
+
* @param {Object} options.extensionManager - The extension manager.
|
|
663
|
+
* @returns {Array} An array containing the SOP class handler module.
|
|
664
|
+
*/
|
|
844
665
|
function getSopClassHandlerModule({
|
|
845
666
|
servicesManager,
|
|
846
667
|
extensionManager
|
|
@@ -852,14 +673,25 @@ function getSopClassHandlerModule({
|
|
|
852
673
|
name: SOPClassHandlerName,
|
|
853
674
|
sopClassUids,
|
|
854
675
|
getDisplaySetsFromSeries
|
|
676
|
+
}, {
|
|
677
|
+
name: SOPClassHandlerName3D,
|
|
678
|
+
sopClassUids: [sopClassDictionary.Comprehensive3DSR],
|
|
679
|
+
getDisplaySetsFromSeries
|
|
855
680
|
}];
|
|
856
681
|
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Retrieves the measurements from the ImagingMeasurementReportContentSequence.
|
|
685
|
+
*
|
|
686
|
+
* @param {Array} ImagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
|
|
687
|
+
* @returns {Array} - The array of measurements.
|
|
688
|
+
*/
|
|
857
689
|
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|
858
|
-
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
690
|
+
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.ImagingMeasurements);
|
|
859
691
|
if (!ImagingMeasurements) {
|
|
860
692
|
return [];
|
|
861
693
|
}
|
|
862
|
-
const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
694
|
+
const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.MeasurementGroup);
|
|
863
695
|
const mergedContentSequencesByTrackingUniqueIdentifiers = _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);
|
|
864
696
|
const measurements = [];
|
|
865
697
|
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(trackingUniqueIdentifier => {
|
|
@@ -871,11 +703,18 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
|
|
871
703
|
});
|
|
872
704
|
return measurements;
|
|
873
705
|
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Retrieves merged content sequences by tracking unique identifiers.
|
|
709
|
+
*
|
|
710
|
+
* @param {Array} MeasurementGroups - The measurement groups.
|
|
711
|
+
* @returns {Object} - The merged content sequences by tracking unique identifiers.
|
|
712
|
+
*/
|
|
874
713
|
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
|
875
714
|
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
|
876
715
|
MeasurementGroups.forEach(MeasurementGroup => {
|
|
877
716
|
const ContentSequence = _getSequenceAsArray(MeasurementGroup.ContentSequence);
|
|
878
|
-
const TrackingUniqueIdentifierItem = ContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
717
|
+
const TrackingUniqueIdentifierItem = ContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.TrackingUniqueIdentifier);
|
|
879
718
|
if (!TrackingUniqueIdentifierItem) {
|
|
880
719
|
console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.');
|
|
881
720
|
}
|
|
@@ -887,7 +726,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
|
|
887
726
|
// Add the ContentSequence minus the tracking identifier, as we have this
|
|
888
727
|
// Information in the merged ContentSequence anyway.
|
|
889
728
|
ContentSequence.forEach(item => {
|
|
890
|
-
if (item.ConceptNameCodeSequence.CodeValue !==
|
|
729
|
+
if (item.ConceptNameCodeSequence.CodeValue !== enums/* CodeNameCodeSequenceValues */.zg.TrackingUniqueIdentifier) {
|
|
891
730
|
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier].push(item);
|
|
892
731
|
}
|
|
893
732
|
});
|
|
@@ -895,19 +734,38 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
|
|
895
734
|
});
|
|
896
735
|
return mergedContentSequencesByTrackingUniqueIdentifiers;
|
|
897
736
|
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Processes the measurement based on the merged content sequence.
|
|
740
|
+
* If the merged content sequence contains SCOORD or SCOORD3D value types,
|
|
741
|
+
* it calls the _processTID1410Measurement function.
|
|
742
|
+
* Otherwise, it calls the _processNonGeometricallyDefinedMeasurement function.
|
|
743
|
+
*
|
|
744
|
+
* @param {Array<Object>} mergedContentSequence - The merged content sequence to process.
|
|
745
|
+
* @returns {any} - The processed measurement result.
|
|
746
|
+
*/
|
|
898
747
|
function _processMeasurement(mergedContentSequence) {
|
|
899
748
|
if (mergedContentSequence.some(group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D')) {
|
|
900
749
|
return _processTID1410Measurement(mergedContentSequence);
|
|
901
750
|
}
|
|
902
751
|
return _processNonGeometricallyDefinedMeasurement(mergedContentSequence);
|
|
903
752
|
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Processes TID 1410 style measurements from the mergedContentSequence.
|
|
756
|
+
* TID 1410 style measurements have a SCOORD or SCOORD3D at the top level,
|
|
757
|
+
* and non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D.
|
|
758
|
+
*
|
|
759
|
+
* @param mergedContentSequence - The merged content sequence containing the measurements.
|
|
760
|
+
* @returns The measurement object containing the loaded status, labels, coordinates, tracking unique identifier, and tracking identifier.
|
|
761
|
+
*/
|
|
904
762
|
function _processTID1410Measurement(mergedContentSequence) {
|
|
905
763
|
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
|
906
764
|
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
|
907
765
|
|
|
908
|
-
const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD');
|
|
766
|
+
const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D');
|
|
909
767
|
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
|
910
|
-
const TrackingIdentifierContentItem = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
768
|
+
const TrackingIdentifierContentItem = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.TrackingIdentifier);
|
|
911
769
|
if (!graphicItem) {
|
|
912
770
|
console.warn(`graphic ValueType ${graphicItem.ValueType} not currently supported, skipping annotation.`);
|
|
913
771
|
return;
|
|
@@ -929,14 +787,28 @@ function _processTID1410Measurement(mergedContentSequence) {
|
|
|
929
787
|
measurement.labels.push(_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence));
|
|
930
788
|
}
|
|
931
789
|
});
|
|
790
|
+
const findingSites = mergedContentSequence.filter(item => item.ConceptNameCodeSequence.CodingSchemeDesignator === enums/* CodingSchemeDesignators */.Ql.SCT && item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.FindingSiteSCT);
|
|
791
|
+
if (findingSites.length) {
|
|
792
|
+
measurement.labels.push({
|
|
793
|
+
label: enums/* CodeNameCodeSequenceValues */.zg.FindingSiteSCT,
|
|
794
|
+
value: findingSites[0].ConceptCodeSequence.CodeMeaning
|
|
795
|
+
});
|
|
796
|
+
}
|
|
932
797
|
return measurement;
|
|
933
798
|
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Processes the non-geometrically defined measurement from the merged content sequence.
|
|
802
|
+
*
|
|
803
|
+
* @param mergedContentSequence The merged content sequence containing the measurement data.
|
|
804
|
+
* @returns The processed measurement object.
|
|
805
|
+
*/
|
|
934
806
|
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|
935
807
|
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
|
936
808
|
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
|
937
|
-
const TrackingIdentifierContentItem = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
938
|
-
const finding = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
939
|
-
const findingSites = mergedContentSequence.filter(item => item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SRT && item.ConceptNameCodeSequence.CodeValue ===
|
|
809
|
+
const TrackingIdentifierContentItem = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.TrackingIdentifier);
|
|
810
|
+
const finding = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.Finding);
|
|
811
|
+
const findingSites = mergedContentSequence.filter(item => item.ConceptNameCodeSequence.CodingSchemeDesignator === enums/* CodingSchemeDesignators */.Ql.SRT && item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.FindingSite);
|
|
940
812
|
const measurement = {
|
|
941
813
|
loaded: false,
|
|
942
814
|
labels: [],
|
|
@@ -944,19 +816,19 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|
|
944
816
|
TrackingUniqueIdentifier: UIDREFContentItem.UID,
|
|
945
817
|
TrackingIdentifier: TrackingIdentifierContentItem.TextValue
|
|
946
818
|
};
|
|
947
|
-
if (finding && CodingSchemeDesignators.CornerstoneCodeSchemes.includes(finding.ConceptCodeSequence.CodingSchemeDesignator) && finding.ConceptCodeSequence.CodeValue ===
|
|
819
|
+
if (finding && enums/* CodingSchemeDesignators */.Ql.CornerstoneCodeSchemes.includes(finding.ConceptCodeSequence.CodingSchemeDesignator) && finding.ConceptCodeSequence.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT) {
|
|
948
820
|
measurement.labels.push({
|
|
949
|
-
label:
|
|
821
|
+
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
|
950
822
|
value: finding.ConceptCodeSequence.CodeMeaning
|
|
951
823
|
});
|
|
952
824
|
}
|
|
953
825
|
|
|
954
826
|
// TODO -> Eventually hopefully support SNOMED or some proper code library, just free text for now.
|
|
955
827
|
if (findingSites.length) {
|
|
956
|
-
const cornerstoneFreeTextFindingSite = findingSites.find(FindingSite => CodingSchemeDesignators.CornerstoneCodeSchemes.includes(FindingSite.ConceptCodeSequence.CodingSchemeDesignator) && FindingSite.ConceptCodeSequence.CodeValue ===
|
|
828
|
+
const cornerstoneFreeTextFindingSite = findingSites.find(FindingSite => enums/* CodingSchemeDesignators */.Ql.CornerstoneCodeSchemes.includes(FindingSite.ConceptCodeSequence.CodingSchemeDesignator) && FindingSite.ConceptCodeSequence.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT);
|
|
957
829
|
if (cornerstoneFreeTextFindingSite) {
|
|
958
830
|
measurement.labels.push({
|
|
959
|
-
label:
|
|
831
|
+
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
|
960
832
|
value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning
|
|
961
833
|
});
|
|
962
834
|
}
|
|
@@ -984,37 +856,37 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
|
|
984
856
|
});
|
|
985
857
|
return measurement;
|
|
986
858
|
}
|
|
987
|
-
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Extracts coordinates from a graphic item of type SCOORD or SCOORD3D.
|
|
862
|
+
* @param {object} graphicItem - The graphic item containing the coordinates.
|
|
863
|
+
* @returns {object} - The extracted coordinates.
|
|
864
|
+
*/
|
|
865
|
+
const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
|
988
866
|
const {
|
|
989
867
|
ValueType,
|
|
990
|
-
RelationshipType,
|
|
991
868
|
GraphicType,
|
|
992
869
|
GraphicData
|
|
993
|
-
} =
|
|
994
|
-
if (!(RelationshipType == RELATIONSHIP_TYPE.INFERRED_FROM || RelationshipType == RELATIONSHIP_TYPE.CONTAINS)) {
|
|
995
|
-
console.warn(`Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM" or "CONTAINS"`);
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
870
|
+
} = graphicItem;
|
|
998
871
|
const coords = {
|
|
999
872
|
ValueType,
|
|
1000
873
|
GraphicType,
|
|
1001
874
|
GraphicData
|
|
1002
875
|
};
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (ValueType === 'SCOORD') {
|
|
1006
|
-
const {
|
|
1007
|
-
ReferencedSOPSequence
|
|
1008
|
-
} = item.ContentSequence;
|
|
1009
|
-
coords.ReferencedSOPSequence = ReferencedSOPSequence;
|
|
1010
|
-
} else if (ValueType === 'SCOORD3D') {
|
|
1011
|
-
const {
|
|
1012
|
-
ReferencedFrameOfReferenceSequence
|
|
1013
|
-
} = item.ContentSequence;
|
|
1014
|
-
coords.ReferencedFrameOfReferenceSequence = ReferencedFrameOfReferenceSequence;
|
|
1015
|
-
}
|
|
876
|
+
coords.ReferencedSOPSequence = graphicItem.ContentSequence?.ReferencedSOPSequence;
|
|
877
|
+
coords.ReferencedFrameOfReferenceSequence = graphicItem.ReferencedFrameOfReferenceUID || graphicItem.ContentSequence?.ReferencedFrameOfReferenceSequence;
|
|
1016
878
|
return coords;
|
|
1017
|
-
}
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Retrieves the label and value from the provided ConceptNameCodeSequence and MeasuredValueSequence.
|
|
883
|
+
* @param {Object} ConceptNameCodeSequence - The ConceptNameCodeSequence object.
|
|
884
|
+
* @param {Object} MeasuredValueSequence - The MeasuredValueSequence object.
|
|
885
|
+
* @returns {Object} - An object containing the label and value.
|
|
886
|
+
* The label represents the CodeMeaning from the ConceptNameCodeSequence.
|
|
887
|
+
* The value represents the formatted NumericValue and CodeValue from the MeasuredValueSequence.
|
|
888
|
+
* Example: { label: 'Long Axis', value: '31.00 mm' }
|
|
889
|
+
*/
|
|
1018
890
|
function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) {
|
|
1019
891
|
const {
|
|
1020
892
|
CodeMeaning
|
|
@@ -1032,12 +904,22 @@ function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredVal
|
|
|
1032
904
|
value: `${formatedNumericValue} ${CodeValue}`
|
|
1033
905
|
}; // E.g. Long Axis: 31.0 mm
|
|
1034
906
|
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Retrieves a list of referenced images from the Imaging Measurement Report Content Sequence.
|
|
910
|
+
*
|
|
911
|
+
* @param {Array} ImagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
|
|
912
|
+
* @returns {Array} - The list of referenced images.
|
|
913
|
+
*/
|
|
1035
914
|
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
|
1036
|
-
const ImageLibrary = ImagingMeasurementReportContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
915
|
+
const ImageLibrary = ImagingMeasurementReportContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.ImageLibrary);
|
|
1037
916
|
if (!ImageLibrary) {
|
|
1038
917
|
return [];
|
|
1039
918
|
}
|
|
1040
|
-
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(item => item.ConceptNameCodeSequence.CodeValue ===
|
|
919
|
+
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(item => item.ConceptNameCodeSequence.CodeValue === enums/* CodeNameCodeSequenceValues */.zg.ImageLibraryGroup);
|
|
920
|
+
if (!ImageLibraryGroup) {
|
|
921
|
+
return [];
|
|
922
|
+
}
|
|
1041
923
|
const referencedImages = [];
|
|
1042
924
|
_getSequenceAsArray(ImageLibraryGroup.ContentSequence).forEach(item => {
|
|
1043
925
|
const {
|
|
@@ -1061,6 +943,16 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
|
|
1061
943
|
});
|
|
1062
944
|
return referencedImages;
|
|
1063
945
|
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Converts a DICOM sequence to an array.
|
|
949
|
+
* If the sequence is null or undefined, an empty array is returned.
|
|
950
|
+
* If the sequence is already an array, it is returned as is.
|
|
951
|
+
* Otherwise, the sequence is wrapped in an array and returned.
|
|
952
|
+
*
|
|
953
|
+
* @param {any} sequence - The DICOM sequence to convert.
|
|
954
|
+
* @returns {any[]} - The converted array.
|
|
955
|
+
*/
|
|
1064
956
|
function _getSequenceAsArray(sequence) {
|
|
1065
957
|
if (!sequence) {
|
|
1066
958
|
return [];
|
|
@@ -1141,7 +1033,7 @@ function onModeEnter({
|
|
|
1141
1033
|
displaySetService
|
|
1142
1034
|
} = servicesManager.services;
|
|
1143
1035
|
const displaySetCache = displaySetService.getDisplaySetCache();
|
|
1144
|
-
const srDisplaySets = [...displaySetCache.values()].filter(ds => ds.SOPClassHandlerId === SOPClassHandlerId);
|
|
1036
|
+
const srDisplaySets = [...displaySetCache.values()].filter(ds => ds.SOPClassHandlerId === SOPClassHandlerId || ds.SOPClassHandlerId === SOPClassHandlerId3D);
|
|
1145
1037
|
srDisplaySets.forEach(ds => {
|
|
1146
1038
|
// New mode route, allow SRs to be hydrated again
|
|
1147
1039
|
ds.isHydrated = false;
|
|
@@ -1196,188 +1088,675 @@ function getFilteredCornerstoneToolState(measurementData, additionalFindingTypes
|
|
|
1196
1088
|
});
|
|
1197
1089
|
}
|
|
1198
1090
|
}
|
|
1199
|
-
if (measurementDataI.findingSites) {
|
|
1200
|
-
findingSites.push(...measurementDataI.findingSites);
|
|
1091
|
+
if (measurementDataI.findingSites) {
|
|
1092
|
+
findingSites.push(...measurementDataI.findingSites);
|
|
1093
|
+
}
|
|
1094
|
+
const measurement = Object.assign({}, annotation, {
|
|
1095
|
+
finding,
|
|
1096
|
+
findingSites
|
|
1097
|
+
});
|
|
1098
|
+
toolData.push(measurement);
|
|
1099
|
+
}
|
|
1100
|
+
const uidFilter = measurementData.map(md => md.uid);
|
|
1101
|
+
const uids = uidFilter.slice();
|
|
1102
|
+
const annotationManager = esm.annotation.state.getAnnotationManager();
|
|
1103
|
+
const framesOfReference = annotationManager.getFramesOfReference();
|
|
1104
|
+
for (let i = 0; i < framesOfReference.length; i++) {
|
|
1105
|
+
const frameOfReference = framesOfReference[i];
|
|
1106
|
+
const frameOfReferenceAnnotations = annotationManager.getAnnotations(frameOfReference);
|
|
1107
|
+
const toolTypes = Object.keys(frameOfReferenceAnnotations);
|
|
1108
|
+
for (let j = 0; j < toolTypes.length; j++) {
|
|
1109
|
+
const toolType = toolTypes[j];
|
|
1110
|
+
const annotations = frameOfReferenceAnnotations[toolType];
|
|
1111
|
+
if (annotations) {
|
|
1112
|
+
for (let k = 0; k < annotations.length; k++) {
|
|
1113
|
+
const annotation = annotations[k];
|
|
1114
|
+
const uidIndex = uids.findIndex(uid => uid === annotation.annotationUID);
|
|
1115
|
+
if (uidIndex !== -1) {
|
|
1116
|
+
addToFilteredToolState(annotation, toolType);
|
|
1117
|
+
uids.splice(uidIndex, 1);
|
|
1118
|
+
if (!uids.length) {
|
|
1119
|
+
return filteredToolState;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return filteredToolState;
|
|
1127
|
+
}
|
|
1128
|
+
/* harmony default export */ const utils_getFilteredCornerstoneToolState = (getFilteredCornerstoneToolState);
|
|
1129
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/commandsModule.ts
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
const {
|
|
1136
|
+
MeasurementReport
|
|
1137
|
+
} = adapters_es.adaptersSR.Cornerstone3D;
|
|
1138
|
+
const {
|
|
1139
|
+
log: commandsModule_log
|
|
1140
|
+
} = src["default"];
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
*
|
|
1144
|
+
* @param measurementData An array of measurements from the measurements service
|
|
1145
|
+
* that you wish to serialize.
|
|
1146
|
+
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
1147
|
+
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1148
|
+
*
|
|
1149
|
+
*/
|
|
1150
|
+
const _generateReport = (measurementData, additionalFindingTypes, options = {}) => {
|
|
1151
|
+
const filteredToolState = utils_getFilteredCornerstoneToolState(measurementData, additionalFindingTypes);
|
|
1152
|
+
const report = MeasurementReport.generateReport(filteredToolState, dist_esm.metaData, dist_esm.utilities.worldToImageCoords, options);
|
|
1153
|
+
const {
|
|
1154
|
+
dataset
|
|
1155
|
+
} = report;
|
|
1156
|
+
|
|
1157
|
+
// Set the default character set as UTF-8
|
|
1158
|
+
// https://dicom.innolitics.com/ciods/nm-image/sop-common/00080005
|
|
1159
|
+
if (typeof dataset.SpecificCharacterSet === 'undefined') {
|
|
1160
|
+
dataset.SpecificCharacterSet = 'ISO_IR 192';
|
|
1161
|
+
}
|
|
1162
|
+
return dataset;
|
|
1163
|
+
};
|
|
1164
|
+
const commandsModule = props => {
|
|
1165
|
+
const {
|
|
1166
|
+
servicesManager
|
|
1167
|
+
} = props;
|
|
1168
|
+
const {
|
|
1169
|
+
customizationService
|
|
1170
|
+
} = servicesManager.services;
|
|
1171
|
+
const actions = {
|
|
1172
|
+
/**
|
|
1173
|
+
*
|
|
1174
|
+
* @param measurementData An array of measurements from the measurements service
|
|
1175
|
+
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
1176
|
+
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1177
|
+
* as opposed to Finding Sites.
|
|
1178
|
+
* that you wish to serialize.
|
|
1179
|
+
*/
|
|
1180
|
+
downloadReport: ({
|
|
1181
|
+
measurementData,
|
|
1182
|
+
additionalFindingTypes,
|
|
1183
|
+
options = {}
|
|
1184
|
+
}) => {
|
|
1185
|
+
const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options);
|
|
1186
|
+
const reportBlob = dcmjs_es["default"].data.datasetToBlob(srDataset);
|
|
1187
|
+
|
|
1188
|
+
//Create a URL for the binary.
|
|
1189
|
+
const objectUrl = URL.createObjectURL(reportBlob);
|
|
1190
|
+
window.location.assign(objectUrl);
|
|
1191
|
+
},
|
|
1192
|
+
/**
|
|
1193
|
+
*
|
|
1194
|
+
* @param measurementData An array of measurements from the measurements service
|
|
1195
|
+
* that you wish to serialize.
|
|
1196
|
+
* @param dataSource The dataSource that you wish to use to persist the data.
|
|
1197
|
+
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
1198
|
+
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1199
|
+
* @return The naturalized report
|
|
1200
|
+
*/
|
|
1201
|
+
storeMeasurements: async ({
|
|
1202
|
+
measurementData,
|
|
1203
|
+
dataSource,
|
|
1204
|
+
additionalFindingTypes,
|
|
1205
|
+
options = {}
|
|
1206
|
+
}) => {
|
|
1207
|
+
// Use the @cornerstonejs adapter for converting to/from DICOM
|
|
1208
|
+
// But it is good enough for now whilst we only have cornerstone as a datasource.
|
|
1209
|
+
commandsModule_log.info('[DICOMSR] storeMeasurements');
|
|
1210
|
+
if (!dataSource || !dataSource.store || !dataSource.store.dicom) {
|
|
1211
|
+
commandsModule_log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!');
|
|
1212
|
+
return Promise.reject({});
|
|
1213
|
+
}
|
|
1214
|
+
try {
|
|
1215
|
+
const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options);
|
|
1216
|
+
const {
|
|
1217
|
+
StudyInstanceUID,
|
|
1218
|
+
ContentSequence
|
|
1219
|
+
} = naturalizedReport;
|
|
1220
|
+
// The content sequence has 5 or more elements, of which
|
|
1221
|
+
// the `[4]` element contains the annotation data, so this is
|
|
1222
|
+
// checking that there is some annotation data present.
|
|
1223
|
+
if (!ContentSequence?.[4].ContentSequence?.length) {
|
|
1224
|
+
console.log('naturalizedReport missing imaging content', naturalizedReport);
|
|
1225
|
+
throw new Error('Invalid report, no content');
|
|
1226
|
+
}
|
|
1227
|
+
const onBeforeDicomStore = customizationService.getModeCustomization('onBeforeDicomStore')?.value;
|
|
1228
|
+
let dicomDict;
|
|
1229
|
+
if (typeof onBeforeDicomStore === 'function') {
|
|
1230
|
+
dicomDict = onBeforeDicomStore({
|
|
1231
|
+
measurementData,
|
|
1232
|
+
naturalizedReport
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
await dataSource.store.dicom(naturalizedReport, null, dicomDict);
|
|
1236
|
+
if (StudyInstanceUID) {
|
|
1237
|
+
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// The "Mode" route listens for DicomMetadataStore changes
|
|
1241
|
+
// When a new instance is added, it listens and
|
|
1242
|
+
// automatically calls makeDisplaySets
|
|
1243
|
+
src.DicomMetadataStore.addInstances([naturalizedReport], true);
|
|
1244
|
+
return naturalizedReport;
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
console.warn(error);
|
|
1247
|
+
commandsModule_log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`);
|
|
1248
|
+
throw new Error(error.message || 'Error while saving the measurements.');
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
const definitions = {
|
|
1253
|
+
downloadReport: {
|
|
1254
|
+
commandFn: actions.downloadReport
|
|
1255
|
+
},
|
|
1256
|
+
storeMeasurements: {
|
|
1257
|
+
commandFn: actions.storeMeasurements
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
return {
|
|
1261
|
+
actions,
|
|
1262
|
+
definitions,
|
|
1263
|
+
defaultContext: 'CORNERSTONE_STRUCTURED_REPORT'
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
/* harmony default export */ const src_commandsModule = (commandsModule);
|
|
1267
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js
|
|
1268
|
+
var dicomSRModule = __webpack_require__(75449);
|
|
1269
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
class DICOMSRDisplayTool extends esm.AnnotationTool {
|
|
1276
|
+
constructor(toolProps = {}, defaultToolProps = {
|
|
1277
|
+
configuration: {}
|
|
1278
|
+
}) {
|
|
1279
|
+
super(toolProps, defaultToolProps);
|
|
1280
|
+
// This tool should not inherit from AnnotationTool and we should not need
|
|
1281
|
+
// to add the following lines.
|
|
1282
|
+
this.isPointNearTool = () => null;
|
|
1283
|
+
this.getHandleNearImagePoint = () => null;
|
|
1284
|
+
this.renderAnnotation = (enabledElement, svgDrawingHelper) => {
|
|
1285
|
+
const {
|
|
1286
|
+
viewport
|
|
1287
|
+
} = enabledElement;
|
|
1288
|
+
const {
|
|
1289
|
+
element
|
|
1290
|
+
} = viewport;
|
|
1291
|
+
let annotations = esm.annotation.state.getAnnotations(this.getToolName(), element);
|
|
1292
|
+
|
|
1293
|
+
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
|
1294
|
+
if (!annotations?.length) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
annotations = this.filterInteractableAnnotationsForElement(element, annotations);
|
|
1298
|
+
if (!annotations?.length) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const trackingUniqueIdentifiersForElement = (0,dicomSRModule/* getTrackingUniqueIdentifiersForElement */.yR)(element);
|
|
1302
|
+
const {
|
|
1303
|
+
activeIndex,
|
|
1304
|
+
trackingUniqueIdentifiers
|
|
1305
|
+
} = trackingUniqueIdentifiersForElement;
|
|
1306
|
+
const activeTrackingUniqueIdentifier = trackingUniqueIdentifiers[activeIndex];
|
|
1307
|
+
|
|
1308
|
+
// Filter toolData to only render the data for the active SR.
|
|
1309
|
+
const filteredAnnotations = annotations.filter(annotation => trackingUniqueIdentifiers.includes(annotation.data?.TrackingUniqueIdentifier));
|
|
1310
|
+
if (!viewport._actors?.size) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
const styleSpecifier = {
|
|
1314
|
+
toolGroupId: this.toolGroupId,
|
|
1315
|
+
toolName: this.getToolName(),
|
|
1316
|
+
viewportId: enabledElement.viewport.id
|
|
1317
|
+
};
|
|
1318
|
+
const {
|
|
1319
|
+
style: annotationStyle
|
|
1320
|
+
} = esm.annotation.config;
|
|
1321
|
+
for (let i = 0; i < filteredAnnotations.length; i++) {
|
|
1322
|
+
const annotation = filteredAnnotations[i];
|
|
1323
|
+
const annotationUID = annotation.annotationUID;
|
|
1324
|
+
const {
|
|
1325
|
+
renderableData,
|
|
1326
|
+
TrackingUniqueIdentifier
|
|
1327
|
+
} = annotation.data;
|
|
1328
|
+
const {
|
|
1329
|
+
referencedImageId
|
|
1330
|
+
} = annotation.metadata;
|
|
1331
|
+
styleSpecifier.annotationUID = annotationUID;
|
|
1332
|
+
const groupStyle = annotationStyle.getToolGroupToolStyles(this.toolGroupId)[this.getToolName()];
|
|
1333
|
+
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
|
1334
|
+
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
|
1335
|
+
const color = TrackingUniqueIdentifier === activeTrackingUniqueIdentifier ? 'rgb(0, 255, 0)' : this.getStyle('color', styleSpecifier, annotation);
|
|
1336
|
+
const options = {
|
|
1337
|
+
color,
|
|
1338
|
+
lineDash,
|
|
1339
|
+
lineWidth,
|
|
1340
|
+
...groupStyle
|
|
1341
|
+
};
|
|
1342
|
+
Object.keys(renderableData).forEach(GraphicType => {
|
|
1343
|
+
const renderableDataForGraphicType = renderableData[GraphicType];
|
|
1344
|
+
let renderMethod;
|
|
1345
|
+
let canvasCoordinatesAdapter;
|
|
1346
|
+
switch (GraphicType) {
|
|
1347
|
+
case enums/* SCOORDTypes */.oQ.POINT:
|
|
1348
|
+
renderMethod = this.renderPoint;
|
|
1349
|
+
break;
|
|
1350
|
+
case enums/* SCOORDTypes */.oQ.MULTIPOINT:
|
|
1351
|
+
renderMethod = this.renderMultipoint;
|
|
1352
|
+
break;
|
|
1353
|
+
case enums/* SCOORDTypes */.oQ.POLYLINE:
|
|
1354
|
+
renderMethod = this.renderPolyLine;
|
|
1355
|
+
break;
|
|
1356
|
+
case enums/* SCOORDTypes */.oQ.CIRCLE:
|
|
1357
|
+
renderMethod = this.renderEllipse;
|
|
1358
|
+
break;
|
|
1359
|
+
case enums/* SCOORDTypes */.oQ.ELLIPSE:
|
|
1360
|
+
renderMethod = this.renderEllipse;
|
|
1361
|
+
canvasCoordinatesAdapter = esm.utilities.math.ellipse.getCanvasEllipseCorners;
|
|
1362
|
+
break;
|
|
1363
|
+
default:
|
|
1364
|
+
throw new Error(`Unsupported GraphicType: ${GraphicType}`);
|
|
1365
|
+
}
|
|
1366
|
+
const canvasCoordinates = renderMethod(svgDrawingHelper, viewport, renderableDataForGraphicType, annotationUID, referencedImageId, options);
|
|
1367
|
+
this.renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, canvasCoordinatesAdapter, annotation, styleSpecifier, options);
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
_getTextBoxLinesFromLabels(labels) {
|
|
1373
|
+
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!
|
|
1374
|
+
|
|
1375
|
+
const labelLength = Math.min(labels.length, 5);
|
|
1376
|
+
const lines = [];
|
|
1377
|
+
for (let i = 0; i < labelLength; i++) {
|
|
1378
|
+
const labelEntry = labels[i];
|
|
1379
|
+
lines.push(`${_labelToShorthand(labelEntry.label)}: ${labelEntry.value}`);
|
|
1380
|
+
}
|
|
1381
|
+
return lines;
|
|
1382
|
+
}
|
|
1383
|
+
renderPolyLine(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
1384
|
+
const drawingOptions = {
|
|
1385
|
+
color: options.color,
|
|
1386
|
+
width: options.lineWidth,
|
|
1387
|
+
lineDash: options.lineDash
|
|
1388
|
+
};
|
|
1389
|
+
let allCanvasCoordinates = [];
|
|
1390
|
+
renderableData.map((data, index) => {
|
|
1391
|
+
const canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
|
1392
|
+
const lineUID = `${index}`;
|
|
1393
|
+
if (canvasCoordinates.length === 2) {
|
|
1394
|
+
esm.drawing.drawLine(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates[0], canvasCoordinates[1], drawingOptions);
|
|
1395
|
+
} else {
|
|
1396
|
+
esm.drawing.drawPolyline(svgDrawingHelper, annotationUID, lineUID, canvasCoordinates, drawingOptions);
|
|
1397
|
+
}
|
|
1398
|
+
allCanvasCoordinates = allCanvasCoordinates.concat(canvasCoordinates);
|
|
1399
|
+
});
|
|
1400
|
+
return allCanvasCoordinates; // used for drawing textBox
|
|
1401
|
+
}
|
|
1402
|
+
renderMultipoint(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
1403
|
+
let canvasCoordinates;
|
|
1404
|
+
renderableData.map((data, index) => {
|
|
1405
|
+
canvasCoordinates = data.map(p => viewport.worldToCanvas(p));
|
|
1406
|
+
const handleGroupUID = '0';
|
|
1407
|
+
esm.drawing.drawHandles(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, {
|
|
1408
|
+
color: options.color
|
|
1409
|
+
});
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
renderPoint(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
1413
|
+
const canvasCoordinates = [];
|
|
1414
|
+
renderableData.map((data, index) => {
|
|
1415
|
+
const point = data[0];
|
|
1416
|
+
// This gives us one point for arrow
|
|
1417
|
+
canvasCoordinates.push(viewport.worldToCanvas(point));
|
|
1418
|
+
if (data[1] !== undefined) {
|
|
1419
|
+
canvasCoordinates.push(viewport.worldToCanvas(data[1]));
|
|
1420
|
+
} else {
|
|
1421
|
+
// We get the other point for the arrow by using the image size
|
|
1422
|
+
const imagePixelModule = dist_esm.metaData.get('imagePixelModule', referencedImageId);
|
|
1423
|
+
let xOffset = 10;
|
|
1424
|
+
let yOffset = 10;
|
|
1425
|
+
if (imagePixelModule) {
|
|
1426
|
+
const {
|
|
1427
|
+
columns,
|
|
1428
|
+
rows
|
|
1429
|
+
} = imagePixelModule;
|
|
1430
|
+
xOffset = columns / 10;
|
|
1431
|
+
yOffset = rows / 10;
|
|
1432
|
+
}
|
|
1433
|
+
const imagePoint = dist_esm.utilities.worldToImageCoords(referencedImageId, point);
|
|
1434
|
+
const arrowEnd = dist_esm.utilities.imageToWorldCoords(referencedImageId, [imagePoint[0] + xOffset, imagePoint[1] + yOffset]);
|
|
1435
|
+
canvasCoordinates.push(viewport.worldToCanvas(arrowEnd));
|
|
1436
|
+
}
|
|
1437
|
+
const arrowUID = `${index}`;
|
|
1438
|
+
|
|
1439
|
+
// Todo: handle drawing probe as probe, currently we are drawing it as an arrow
|
|
1440
|
+
esm.drawing.drawArrow(svgDrawingHelper, annotationUID, arrowUID, canvasCoordinates[1], canvasCoordinates[0], {
|
|
1441
|
+
color: options.color,
|
|
1442
|
+
width: options.lineWidth
|
|
1443
|
+
});
|
|
1444
|
+
});
|
|
1445
|
+
return canvasCoordinates; // used for drawing textBox
|
|
1446
|
+
}
|
|
1447
|
+
renderEllipse(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
|
|
1448
|
+
let canvasCoordinates;
|
|
1449
|
+
renderableData.map((data, index) => {
|
|
1450
|
+
if (data.length === 0) {
|
|
1451
|
+
// since oblique ellipse is not supported for hydration right now
|
|
1452
|
+
// we just return
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const ellipsePointsWorld = data;
|
|
1456
|
+
const rotation = viewport.getRotation();
|
|
1457
|
+
canvasCoordinates = ellipsePointsWorld.map(p => viewport.worldToCanvas(p));
|
|
1458
|
+
let canvasCorners;
|
|
1459
|
+
if (rotation == 90 || rotation == 270) {
|
|
1460
|
+
canvasCorners = esm.utilities.math.ellipse.getCanvasEllipseCorners([canvasCoordinates[2], canvasCoordinates[3], canvasCoordinates[0], canvasCoordinates[1]]);
|
|
1461
|
+
} else {
|
|
1462
|
+
canvasCorners = esm.utilities.math.ellipse.getCanvasEllipseCorners(canvasCoordinates);
|
|
1463
|
+
}
|
|
1464
|
+
const lineUID = `${index}`;
|
|
1465
|
+
esm.drawing.drawEllipse(svgDrawingHelper, annotationUID, lineUID, canvasCorners[0], canvasCorners[1], {
|
|
1466
|
+
color: options.color,
|
|
1467
|
+
width: options.lineWidth,
|
|
1468
|
+
lineDash: options.lineDash
|
|
1469
|
+
});
|
|
1470
|
+
});
|
|
1471
|
+
return canvasCoordinates;
|
|
1472
|
+
}
|
|
1473
|
+
renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, canvasCoordinatesAdapter, annotation, styleSpecifier, options = {}) {
|
|
1474
|
+
if (!canvasCoordinates || !annotation) {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
const {
|
|
1478
|
+
annotationUID,
|
|
1479
|
+
data = {}
|
|
1480
|
+
} = annotation;
|
|
1481
|
+
const {
|
|
1482
|
+
labels
|
|
1483
|
+
} = data;
|
|
1484
|
+
const {
|
|
1485
|
+
color
|
|
1486
|
+
} = options;
|
|
1487
|
+
let adaptedCanvasCoordinates = canvasCoordinates;
|
|
1488
|
+
// adapt coordinates if there is an adapter
|
|
1489
|
+
if (typeof canvasCoordinatesAdapter === 'function') {
|
|
1490
|
+
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
|
1491
|
+
}
|
|
1492
|
+
const textLines = this._getTextBoxLinesFromLabels(labels);
|
|
1493
|
+
const canvasTextBoxCoords = esm.utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
|
1494
|
+
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
|
1495
|
+
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
|
1496
|
+
}
|
|
1497
|
+
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
|
1498
|
+
const textBoxUID = '1';
|
|
1499
|
+
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
|
1500
|
+
const boundingBox = esm.drawing.drawLinkedTextBox(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, canvasCoordinates, {}, {
|
|
1501
|
+
...textBoxOptions,
|
|
1502
|
+
color
|
|
1503
|
+
});
|
|
1504
|
+
const {
|
|
1505
|
+
x: left,
|
|
1506
|
+
y: top,
|
|
1507
|
+
width,
|
|
1508
|
+
height
|
|
1509
|
+
} = boundingBox;
|
|
1510
|
+
annotation.data.handles.textBox.worldBoundingBox = {
|
|
1511
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
1512
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
1513
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
1514
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height])
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
DICOMSRDisplayTool.toolName = tools_toolNames.DICOMSRDisplay;
|
|
1519
|
+
const SHORT_HAND_MAP = {
|
|
1520
|
+
'Short Axis': 'W: ',
|
|
1521
|
+
'Long Axis': 'L: ',
|
|
1522
|
+
AREA: 'Area: ',
|
|
1523
|
+
Length: '',
|
|
1524
|
+
CORNERSTONEFREETEXT: ''
|
|
1525
|
+
};
|
|
1526
|
+
function _labelToShorthand(label) {
|
|
1527
|
+
const shortHand = SHORT_HAND_MAP[label];
|
|
1528
|
+
if (shortHand !== undefined) {
|
|
1529
|
+
return shortHand;
|
|
1530
|
+
}
|
|
1531
|
+
return label;
|
|
1532
|
+
}
|
|
1533
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/SCOORD3DPointTool.ts
|
|
1534
|
+
|
|
1535
|
+
|
|
1536
|
+
class SCOORD3DPointTool extends esm.AnnotationDisplayTool {
|
|
1537
|
+
constructor(toolProps = {}, defaultToolProps = {
|
|
1538
|
+
configuration: {}
|
|
1539
|
+
}) {
|
|
1540
|
+
super(toolProps, defaultToolProps);
|
|
1541
|
+
// This tool should not inherit from AnnotationTool and we should not need
|
|
1542
|
+
// to add the following lines.
|
|
1543
|
+
this.isPointNearTool = () => null;
|
|
1544
|
+
this.getHandleNearImagePoint = () => null;
|
|
1545
|
+
this.renderAnnotation = (enabledElement, svgDrawingHelper) => {
|
|
1546
|
+
const {
|
|
1547
|
+
viewport
|
|
1548
|
+
} = enabledElement;
|
|
1549
|
+
const {
|
|
1550
|
+
element
|
|
1551
|
+
} = viewport;
|
|
1552
|
+
const annotations = esm.annotation.state.getAnnotations(this.getToolName(), element);
|
|
1553
|
+
|
|
1554
|
+
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
|
1555
|
+
if (!annotations?.length) {
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Filter toolData to only render the data for the active SR.
|
|
1560
|
+
const filteredAnnotations = annotations;
|
|
1561
|
+
if (!viewport._actors?.size) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const styleSpecifier = {
|
|
1565
|
+
toolGroupId: this.toolGroupId,
|
|
1566
|
+
toolName: this.getToolName(),
|
|
1567
|
+
viewportId: enabledElement.viewport.id
|
|
1568
|
+
};
|
|
1569
|
+
for (let i = 0; i < filteredAnnotations.length; i++) {
|
|
1570
|
+
const annotation = filteredAnnotations[i];
|
|
1571
|
+
const annotationUID = annotation.annotationUID;
|
|
1572
|
+
const {
|
|
1573
|
+
renderableData
|
|
1574
|
+
} = annotation.data;
|
|
1575
|
+
const {
|
|
1576
|
+
POINT: points
|
|
1577
|
+
} = renderableData;
|
|
1578
|
+
styleSpecifier.annotationUID = annotationUID;
|
|
1579
|
+
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
|
1580
|
+
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
|
1581
|
+
const color = this.getStyle('color', styleSpecifier, annotation);
|
|
1582
|
+
const options = {
|
|
1583
|
+
color,
|
|
1584
|
+
lineDash,
|
|
1585
|
+
lineWidth
|
|
1586
|
+
};
|
|
1587
|
+
const point = points[0][0];
|
|
1588
|
+
|
|
1589
|
+
// check if viewport can render it
|
|
1590
|
+
const viewable = viewport.isReferenceViewable({
|
|
1591
|
+
FrameOfReferenceUID: annotation.metadata.FrameOfReferenceUID,
|
|
1592
|
+
cameraFocalPoint: point
|
|
1593
|
+
}, {
|
|
1594
|
+
asNearbyProjection: true
|
|
1595
|
+
});
|
|
1596
|
+
if (!viewable) {
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
// render the point
|
|
1601
|
+
const arrowPointCanvas = viewport.worldToCanvas(point);
|
|
1602
|
+
// Todo: configure this
|
|
1603
|
+
const arrowEndCanvas = [arrowPointCanvas[0] + 20, arrowPointCanvas[1] + 20];
|
|
1604
|
+
const canvasCoordinates = [arrowPointCanvas, arrowEndCanvas];
|
|
1605
|
+
esm.drawing.drawArrow(svgDrawingHelper, annotationUID, '1', canvasCoordinates[1], canvasCoordinates[0], {
|
|
1606
|
+
color: options.color,
|
|
1607
|
+
width: options.lineWidth
|
|
1608
|
+
});
|
|
1609
|
+
this.renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, annotation, styleSpecifier, options);
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
_getTextBoxLinesFromLabels(labels) {
|
|
1614
|
+
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!
|
|
1615
|
+
|
|
1616
|
+
const labelLength = Math.min(labels.length, 5);
|
|
1617
|
+
const lines = [];
|
|
1618
|
+
return lines;
|
|
1619
|
+
}
|
|
1620
|
+
renderTextBox(svgDrawingHelper, viewport, canvasCoordinates, annotation, styleSpecifier, options = {}) {
|
|
1621
|
+
if (!canvasCoordinates || !annotation) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const {
|
|
1625
|
+
annotationUID,
|
|
1626
|
+
data = {}
|
|
1627
|
+
} = annotation;
|
|
1628
|
+
const {
|
|
1629
|
+
labels
|
|
1630
|
+
} = data;
|
|
1631
|
+
const textLines = [];
|
|
1632
|
+
for (const label of labels) {
|
|
1633
|
+
// make this generic
|
|
1634
|
+
// fix this
|
|
1635
|
+
if (label.label === '363698007') {
|
|
1636
|
+
textLines.push(`Finding Site: ${label.value}`);
|
|
1637
|
+
}
|
|
1201
1638
|
}
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1639
|
+
const {
|
|
1640
|
+
color
|
|
1641
|
+
} = options;
|
|
1642
|
+
const adaptedCanvasCoordinates = canvasCoordinates;
|
|
1643
|
+
// adapt coordinates if there is an adapter
|
|
1644
|
+
const canvasTextBoxCoords = esm.utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
|
1645
|
+
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
|
1646
|
+
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
|
1647
|
+
}
|
|
1648
|
+
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
|
1649
|
+
const textBoxUID = '1';
|
|
1650
|
+
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
|
1651
|
+
const boundingBox = esm.drawing.drawLinkedTextBox(svgDrawingHelper, annotationUID, textBoxUID, textLines, textBoxPosition, canvasCoordinates, {}, {
|
|
1652
|
+
...textBoxOptions,
|
|
1653
|
+
color
|
|
1205
1654
|
});
|
|
1206
|
-
|
|
1655
|
+
const {
|
|
1656
|
+
x: left,
|
|
1657
|
+
y: top,
|
|
1658
|
+
width,
|
|
1659
|
+
height
|
|
1660
|
+
} = boundingBox;
|
|
1661
|
+
annotation.data.handles.textBox.worldBoundingBox = {
|
|
1662
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
1663
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
1664
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
1665
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height])
|
|
1666
|
+
};
|
|
1207
1667
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
const uidIndex = uids.findIndex(uid => uid === annotation.annotationUID);
|
|
1223
|
-
if (uidIndex !== -1) {
|
|
1224
|
-
addToFilteredToolState(annotation, toolType);
|
|
1225
|
-
uids.splice(uidIndex, 1);
|
|
1226
|
-
if (!uids.length) {
|
|
1227
|
-
return filteredToolState;
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1668
|
+
getLinkedTextBoxStyle(specifications, annotation) {
|
|
1669
|
+
// Todo: this function can be used to set different styles for different toolMode
|
|
1670
|
+
// for the textBox.
|
|
1671
|
+
|
|
1672
|
+
return {
|
|
1673
|
+
visibility: this.getStyle('textBoxVisibility', specifications, annotation),
|
|
1674
|
+
fontFamily: this.getStyle('textBoxFontFamily', specifications, annotation),
|
|
1675
|
+
fontSize: this.getStyle('textBoxFontSize', specifications, annotation),
|
|
1676
|
+
color: this.getStyle('textBoxColor', specifications, annotation),
|
|
1677
|
+
shadow: this.getStyle('textBoxShadow', specifications, annotation),
|
|
1678
|
+
background: this.getStyle('textBoxBackground', specifications, annotation),
|
|
1679
|
+
lineWidth: this.getStyle('textBoxLinkLineWidth', specifications, annotation),
|
|
1680
|
+
lineDash: this.getStyle('textBoxLinkLineDash', specifications, annotation)
|
|
1681
|
+
};
|
|
1233
1682
|
}
|
|
1234
|
-
return filteredToolState;
|
|
1235
1683
|
}
|
|
1236
|
-
|
|
1237
|
-
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1684
|
+
SCOORD3DPointTool.toolName = tools_toolNames.SRSCOORD3DPoint;
|
|
1685
|
+
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/SRSCOOR3DProbeMapper.ts
|
|
1686
|
+
const SRSCOOR3DProbe = {
|
|
1687
|
+
toAnnotation: measurement => {},
|
|
1688
|
+
/**
|
|
1689
|
+
* Maps cornerstone annotation event data to measurement service format.
|
|
1690
|
+
*
|
|
1691
|
+
* @param {Object} cornerstone Cornerstone event data
|
|
1692
|
+
* @return {Measurement} Measurement instance
|
|
1693
|
+
*/
|
|
1694
|
+
toMeasurement: (csToolsEventDetail, displaySetService, CornerstoneViewportService, getValueTypeFromToolType, customizationService) => {
|
|
1695
|
+
const {
|
|
1696
|
+
annotation
|
|
1697
|
+
} = csToolsEventDetail;
|
|
1698
|
+
const {
|
|
1699
|
+
metadata,
|
|
1700
|
+
data,
|
|
1701
|
+
annotationUID
|
|
1702
|
+
} = annotation;
|
|
1703
|
+
if (!metadata || !data) {
|
|
1704
|
+
console.warn('Probe tool: Missing metadata or data');
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
const {
|
|
1708
|
+
toolName
|
|
1709
|
+
} = metadata;
|
|
1710
|
+
const {
|
|
1711
|
+
points
|
|
1712
|
+
} = data.handles;
|
|
1713
|
+
const displayText = getDisplayText(annotation);
|
|
1714
|
+
return {
|
|
1715
|
+
uid: annotationUID,
|
|
1716
|
+
points,
|
|
1717
|
+
metadata,
|
|
1718
|
+
toolName: metadata.toolName,
|
|
1719
|
+
label: data.label,
|
|
1720
|
+
displayText: displayText,
|
|
1721
|
+
data: data.cachedStats,
|
|
1722
|
+
type: getValueTypeFromToolType?.(toolName) ?? null
|
|
1723
|
+
};
|
|
1269
1724
|
}
|
|
1270
|
-
return dataset;
|
|
1271
1725
|
};
|
|
1272
|
-
|
|
1726
|
+
function getDisplayText(annotation) {
|
|
1273
1727
|
const {
|
|
1274
|
-
|
|
1275
|
-
} =
|
|
1728
|
+
data
|
|
1729
|
+
} = annotation;
|
|
1730
|
+
if (!data) {
|
|
1731
|
+
return [''];
|
|
1732
|
+
}
|
|
1276
1733
|
const {
|
|
1277
|
-
|
|
1278
|
-
} =
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1285
|
-
* as opposed to Finding Sites.
|
|
1286
|
-
* that you wish to serialize.
|
|
1287
|
-
*/
|
|
1288
|
-
downloadReport: ({
|
|
1289
|
-
measurementData,
|
|
1290
|
-
additionalFindingTypes,
|
|
1291
|
-
options = {}
|
|
1292
|
-
}) => {
|
|
1293
|
-
const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options);
|
|
1294
|
-
const reportBlob = dcmjs_es["default"].data.datasetToBlob(srDataset);
|
|
1295
|
-
|
|
1296
|
-
//Create a URL for the binary.
|
|
1297
|
-
const objectUrl = URL.createObjectURL(reportBlob);
|
|
1298
|
-
window.location.assign(objectUrl);
|
|
1299
|
-
},
|
|
1300
|
-
/**
|
|
1301
|
-
*
|
|
1302
|
-
* @param measurementData An array of measurements from the measurements service
|
|
1303
|
-
* that you wish to serialize.
|
|
1304
|
-
* @param dataSource The dataSource that you wish to use to persist the data.
|
|
1305
|
-
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
|
|
1306
|
-
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
|
|
1307
|
-
* @return The naturalized report
|
|
1308
|
-
*/
|
|
1309
|
-
storeMeasurements: async ({
|
|
1310
|
-
measurementData,
|
|
1311
|
-
dataSource,
|
|
1312
|
-
additionalFindingTypes,
|
|
1313
|
-
options = {}
|
|
1314
|
-
}) => {
|
|
1315
|
-
// Use the @cornerstonejs adapter for converting to/from DICOM
|
|
1316
|
-
// But it is good enough for now whilst we only have cornerstone as a datasource.
|
|
1317
|
-
commandsModule_log.info('[DICOMSR] storeMeasurements');
|
|
1318
|
-
if (!dataSource || !dataSource.store || !dataSource.store.dicom) {
|
|
1319
|
-
commandsModule_log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!');
|
|
1320
|
-
return Promise.reject({});
|
|
1321
|
-
}
|
|
1322
|
-
try {
|
|
1323
|
-
const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options);
|
|
1324
|
-
const {
|
|
1325
|
-
StudyInstanceUID,
|
|
1326
|
-
ContentSequence
|
|
1327
|
-
} = naturalizedReport;
|
|
1328
|
-
// The content sequence has 5 or more elements, of which
|
|
1329
|
-
// the `[4]` element contains the annotation data, so this is
|
|
1330
|
-
// checking that there is some annotation data present.
|
|
1331
|
-
if (!ContentSequence?.[4].ContentSequence?.length) {
|
|
1332
|
-
console.log('naturalizedReport missing imaging content', naturalizedReport);
|
|
1333
|
-
throw new Error('Invalid report, no content');
|
|
1334
|
-
}
|
|
1335
|
-
const onBeforeDicomStore = customizationService.getModeCustomization('onBeforeDicomStore')?.value;
|
|
1336
|
-
let dicomDict;
|
|
1337
|
-
if (typeof onBeforeDicomStore === 'function') {
|
|
1338
|
-
dicomDict = onBeforeDicomStore({
|
|
1339
|
-
measurementData,
|
|
1340
|
-
naturalizedReport
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
await dataSource.store.dicom(naturalizedReport, null, dicomDict);
|
|
1344
|
-
if (StudyInstanceUID) {
|
|
1345
|
-
dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// The "Mode" route listens for DicomMetadataStore changes
|
|
1349
|
-
// When a new instance is added, it listens and
|
|
1350
|
-
// automatically calls makeDisplaySets
|
|
1351
|
-
src.DicomMetadataStore.addInstances([naturalizedReport], true);
|
|
1352
|
-
return naturalizedReport;
|
|
1353
|
-
} catch (error) {
|
|
1354
|
-
console.warn(error);
|
|
1355
|
-
commandsModule_log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`);
|
|
1356
|
-
throw new Error(error.message || 'Error while saving the measurements.');
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
const definitions = {
|
|
1361
|
-
downloadReport: {
|
|
1362
|
-
commandFn: actions.downloadReport
|
|
1363
|
-
},
|
|
1364
|
-
storeMeasurements: {
|
|
1365
|
-
commandFn: actions.storeMeasurements
|
|
1734
|
+
labels
|
|
1735
|
+
} = data;
|
|
1736
|
+
const displayText = [];
|
|
1737
|
+
for (const label of labels) {
|
|
1738
|
+
// make this generic
|
|
1739
|
+
if (label.label === '33636980076') {
|
|
1740
|
+
displayText.push(`Finding Site: ${label.value}`);
|
|
1366
1741
|
}
|
|
1367
|
-
}
|
|
1368
|
-
return
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
defaultContext: 'CORNERSTONE_STRUCTURED_REPORT'
|
|
1372
|
-
};
|
|
1373
|
-
};
|
|
1374
|
-
/* harmony default export */ const src_commandsModule = (commandsModule);
|
|
1742
|
+
}
|
|
1743
|
+
return displayText;
|
|
1744
|
+
}
|
|
1745
|
+
/* harmony default export */ const SRSCOOR3DProbeMapper = (SRSCOOR3DProbe);
|
|
1375
1746
|
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts
|
|
1376
1747
|
|
|
1377
|
-
function addToolInstance(name, toolClass, configuration) {
|
|
1378
|
-
class InstanceClass extends toolClass {
|
|
1748
|
+
function addToolInstance(name, toolClass, configuration = {}) {
|
|
1749
|
+
class InstanceClass extends toolClass {
|
|
1750
|
+
constructor(toolProps, defaultToolProps) {
|
|
1751
|
+
toolProps.configuration = toolProps.configuration ? {
|
|
1752
|
+
...toolProps.configuration,
|
|
1753
|
+
...configuration
|
|
1754
|
+
} : configuration;
|
|
1755
|
+
super(toolProps, defaultToolProps);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1379
1758
|
InstanceClass.toolName = name;
|
|
1380
|
-
(0,
|
|
1759
|
+
(0,esm.addTool)(InstanceClass);
|
|
1381
1760
|
}
|
|
1382
1761
|
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/init.ts
|
|
1383
1762
|
|
|
@@ -1385,30 +1764,49 @@ function addToolInstance(name, toolClass, configuration) {
|
|
|
1385
1764
|
|
|
1386
1765
|
|
|
1387
1766
|
|
|
1767
|
+
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
const {
|
|
1771
|
+
CORNERSTONE_3D_TOOLS_SOURCE_NAME: init_CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
|
1772
|
+
CORNERSTONE_3D_TOOLS_SOURCE_VERSION: init_CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
|
1773
|
+
} = cornerstone_src.Enums;
|
|
1774
|
+
|
|
1388
1775
|
/**
|
|
1389
1776
|
* @param {object} configuration
|
|
1390
1777
|
*/
|
|
1391
1778
|
function init({
|
|
1392
|
-
configuration = {}
|
|
1779
|
+
configuration = {},
|
|
1780
|
+
servicesManager
|
|
1393
1781
|
}) {
|
|
1782
|
+
const {
|
|
1783
|
+
measurementService,
|
|
1784
|
+
cornerstoneViewportService
|
|
1785
|
+
} = servicesManager.services;
|
|
1394
1786
|
addToolInstance(tools_toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
|
|
1395
|
-
addToolInstance(tools_toolNames.SRLength,
|
|
1396
|
-
addToolInstance(tools_toolNames.SRBidirectional,
|
|
1397
|
-
addToolInstance(tools_toolNames.SREllipticalROI,
|
|
1398
|
-
addToolInstance(tools_toolNames.SRCircleROI,
|
|
1399
|
-
addToolInstance(tools_toolNames.SRArrowAnnotate,
|
|
1400
|
-
addToolInstance(tools_toolNames.SRAngle,
|
|
1401
|
-
addToolInstance(tools_toolNames.SRPlanarFreehandROI,
|
|
1402
|
-
addToolInstance(tools_toolNames.SRRectangleROI,
|
|
1787
|
+
addToolInstance(tools_toolNames.SRLength, esm.LengthTool);
|
|
1788
|
+
addToolInstance(tools_toolNames.SRBidirectional, esm.BidirectionalTool);
|
|
1789
|
+
addToolInstance(tools_toolNames.SREllipticalROI, esm.EllipticalROITool);
|
|
1790
|
+
addToolInstance(tools_toolNames.SRCircleROI, esm.CircleROITool);
|
|
1791
|
+
addToolInstance(tools_toolNames.SRArrowAnnotate, esm.ArrowAnnotateTool);
|
|
1792
|
+
addToolInstance(tools_toolNames.SRAngle, esm.AngleTool);
|
|
1793
|
+
addToolInstance(tools_toolNames.SRPlanarFreehandROI, esm.PlanarFreehandROITool);
|
|
1794
|
+
addToolInstance(tools_toolNames.SRRectangleROI, esm.RectangleROITool);
|
|
1795
|
+
addToolInstance(tools_toolNames.SRSCOORD3DPoint, SCOORD3DPointTool);
|
|
1403
1796
|
|
|
1404
1797
|
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
|
|
1405
|
-
addToolInstance(tools_toolNames.SRCobbAngle,
|
|
1798
|
+
addToolInstance(tools_toolNames.SRCobbAngle, esm.CobbAngleTool);
|
|
1799
|
+
const csTools3DVer1MeasurementSource = measurementService.getSource(init_CORNERSTONE_3D_TOOLS_SOURCE_NAME, init_CORNERSTONE_3D_TOOLS_SOURCE_VERSION);
|
|
1800
|
+
const {
|
|
1801
|
+
POINT
|
|
1802
|
+
} = measurementService.VALUE_TYPES;
|
|
1803
|
+
measurementService.addMapping(csTools3DVer1MeasurementSource, 'SRSCOORD3DPoint', POINT, SRSCOOR3DProbeMapper.toAnnotation, SRSCOOR3DProbeMapper.toMeasurement);
|
|
1406
1804
|
|
|
1407
1805
|
// Modify annotation tools to use dashed lines on SR
|
|
1408
1806
|
const dashedLine = {
|
|
1409
1807
|
lineDash: '4,4'
|
|
1410
1808
|
};
|
|
1411
|
-
|
|
1809
|
+
esm.annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
|
|
1412
1810
|
[tools_toolNames.DICOMSRDisplay]: dashedLine,
|
|
1413
1811
|
SRLength: dashedLine,
|
|
1414
1812
|
SRBidirectional: dashedLine,
|
|
@@ -1421,6 +1819,28 @@ function init({
|
|
|
1421
1819
|
SRRectangleROI: dashedLine,
|
|
1422
1820
|
global: {}
|
|
1423
1821
|
});
|
|
1822
|
+
measurementService.subscribe(src.MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT, ({
|
|
1823
|
+
viewportId,
|
|
1824
|
+
measurement,
|
|
1825
|
+
isConsumed
|
|
1826
|
+
}) => {
|
|
1827
|
+
if (isConsumed) {
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
try {
|
|
1831
|
+
const currentViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
|
1832
|
+
const {
|
|
1833
|
+
viewPlaneNormal
|
|
1834
|
+
} = currentViewport.getCamera();
|
|
1835
|
+
const referencedImageId = esm.utilities.getClosestImageIdForStackViewport(currentViewport, measurement.points[0], viewPlaneNormal);
|
|
1836
|
+
const imageIndex = currentViewport.getImageIds().indexOf(referencedImageId);
|
|
1837
|
+
esm.utilities.jumpToSlice(currentViewport.element, {
|
|
1838
|
+
imageIndex
|
|
1839
|
+
});
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
console.warn('Unable to jump to image based on measurement coordinate', error);
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1424
1844
|
}
|
|
1425
1845
|
// EXTERNAL MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts + 1 modules
|
|
1426
1846
|
var hydrateStructuredReport = __webpack_require__(83330);
|
|
@@ -1438,6 +1858,7 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
|
|
|
1438
1858
|
|
|
1439
1859
|
|
|
1440
1860
|
|
|
1861
|
+
|
|
1441
1862
|
const Component = /*#__PURE__*/react.lazy(() => {
|
|
1442
1863
|
return __webpack_require__.e(/* import() */ 8714).then(__webpack_require__.bind(__webpack_require__, 78714));
|
|
1443
1864
|
});
|
|
@@ -1565,6 +1986,9 @@ const createReferencedImageDisplaySet = (displaySetService, displaySet) => {
|
|
|
1565
1986
|
};
|
|
1566
1987
|
const imageSet = new ImageSet(instances);
|
|
1567
1988
|
const instance = instances[0];
|
|
1989
|
+
if (!instance) {
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1568
1992
|
imageSet.setAttributes({
|
|
1569
1993
|
displaySetInstanceUID: imageSet.uid,
|
|
1570
1994
|
// create a local alias for the imageSet UID
|
|
@@ -1606,7 +2030,14 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
1606
2030
|
var esm = __webpack_require__(12651);
|
|
1607
2031
|
// EXTERNAL MODULE: ../../core/src/index.ts + 74 modules
|
|
1608
2032
|
var src = __webpack_require__(84793);
|
|
2033
|
+
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/adapters.es.js
|
|
2034
|
+
var adapters_es = __webpack_require__(91202);
|
|
1609
2035
|
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js
|
|
2036
|
+
|
|
2037
|
+
const {
|
|
2038
|
+
CodeScheme: Cornerstone3DCodeScheme
|
|
2039
|
+
} = adapters_es.adaptersSR.Cornerstone3D;
|
|
2040
|
+
|
|
1610
2041
|
/**
|
|
1611
2042
|
* Extracts the label from the toolData imported from dcmjs. We need to do this
|
|
1612
2043
|
* as dcmjs does not depeend on OHIF/the measurementService, it just produces data for cornestoneTools.
|
|
@@ -1620,24 +2051,25 @@ function getLabelFromDCMJSImportedToolData(toolData) {
|
|
|
1620
2051
|
findingSites = [],
|
|
1621
2052
|
finding
|
|
1622
2053
|
} = toolData;
|
|
1623
|
-
let freeTextLabel = findingSites.find(fs => fs.CodeValue ===
|
|
2054
|
+
let freeTextLabel = findingSites.find(fs => fs.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT);
|
|
1624
2055
|
if (freeTextLabel) {
|
|
1625
2056
|
return freeTextLabel.CodeMeaning;
|
|
1626
2057
|
}
|
|
1627
|
-
if (finding && finding.CodeValue ===
|
|
2058
|
+
if (finding && finding.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT) {
|
|
1628
2059
|
return finding.CodeMeaning;
|
|
1629
2060
|
}
|
|
1630
2061
|
}
|
|
1631
|
-
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/adapters.es.js
|
|
1632
|
-
var adapters_es = __webpack_require__(91202);
|
|
1633
2062
|
// EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js
|
|
1634
2063
|
var dist_esm = __webpack_require__(93725);
|
|
2064
|
+
// EXTERNAL MODULE: ../../../extensions/cornerstone/src/index.tsx + 96 modules
|
|
2065
|
+
var cornerstone_src = __webpack_require__(83846);
|
|
1635
2066
|
;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.ts
|
|
1636
2067
|
|
|
1637
2068
|
|
|
1638
2069
|
|
|
1639
2070
|
|
|
1640
2071
|
|
|
2072
|
+
|
|
1641
2073
|
const {
|
|
1642
2074
|
locking
|
|
1643
2075
|
} = dist_esm.annotation;
|
|
@@ -1648,8 +2080,10 @@ const {
|
|
|
1648
2080
|
MeasurementReport,
|
|
1649
2081
|
CORNERSTONE_3D_TAG
|
|
1650
2082
|
} = adapters_es.adaptersSR.Cornerstone3D;
|
|
1651
|
-
const
|
|
1652
|
-
|
|
2083
|
+
const {
|
|
2084
|
+
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
|
2085
|
+
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
|
2086
|
+
} = cornerstone_src.Enums;
|
|
1653
2087
|
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
|
1654
2088
|
const convertCode = (codingValues, code) => {
|
|
1655
2089
|
if (!code || code.CodingSchemeDesignator === 'CORNERSTONEJS') {
|