@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.
Files changed (43) hide show
  1. package/dist/{1169.bundle.4622fde2805592aad5d0.js → 1169.bundle.787863a90ed935004d9d.js} +2 -1
  2. package/dist/{4842.bundle.6188ba4a4aca12aaf7d1.js → 1841.bundle.b644430a7b443b715aee.js} +11 -11
  3. package/dist/1841.css +3 -0
  4. package/dist/{2119.bundle.6aca0cf4fc5db62d77b9.js → 2119.bundle.112adcabafef89bc3d13.js} +3 -3
  5. package/dist/2173.css +3 -0
  6. package/dist/{2650.bundle.feb3b241748314884ae4.js → 2650.bundle.74fd325955c61b0311ec.js} +793 -30
  7. package/dist/{3117.bundle.463d5fa4671a93939d36.js → 3117.bundle.d76671aced0d2669817a.js} +2 -562
  8. package/dist/{962.bundle.b60b094cbfd0486819ce.js → 3846.bundle.559405e95050bff1bdb8.js} +111 -75
  9. package/dist/{3962.bundle.9a1d66291b3070873892.js → 3962.bundle.bbb70fcdf211b7044025.js} +2 -2
  10. package/dist/{4210.bundle.ccccde45071c91a381b5.js → 4210.bundle.a41a375b496c7019fbd9.js} +3 -3
  11. package/dist/{7360.bundle.70420db3a8b2396a74ca.js → 4566.bundle.ec9936198e788f67bd79.js} +1210 -776
  12. package/dist/{5888.bundle.687c9173dc3dbca67b6c.js → 5888.bundle.345f92c3ccc69e0c4543.js} +4 -6
  13. package/dist/{6552.bundle.286dda309441db5b56c1.js → 6552.bundle.87f348d1180c24315677.js} +35 -23
  14. package/dist/{3482.bundle.0b9c4d64f4b14af4ee0a.js → 6558.bundle.b68e53ad9d4cfabea140.js} +44 -52
  15. package/dist/6558.css +3 -0
  16. package/dist/{6904.bundle.da0d83fcd48a48cfaac1.js → 6904.bundle.2daa01969c25260fb947.js} +51 -29
  17. package/dist/{818.bundle.b6027359846cc90e32ff.js → 818.bundle.54e9bb8d6df15d139927.js} +10 -0
  18. package/dist/{8714.bundle.36331faa35db985a786c.js → 8714.bundle.9915effb1c16f334a69b.js} +17 -5
  19. package/dist/{3497.bundle.512610422a2862584c55.js → 896.bundle.0300bc2ea91c7160248d.js} +1 -560
  20. package/dist/{8993.bundle.23cd5f8560800a97d798.js → 8993.bundle.fc59969a3dfc25255a3a.js} +735 -26
  21. package/dist/{8999.bundle.a8701d5a00ceb84bab64.js → 8999.bundle.e936840b7caf9dae422e.js} +3 -3
  22. package/dist/{9579.bundle.f9c9be0c5ab6cddbde3d.js → 9579.bundle.8bb193d6ff0f63a19c72.js} +11 -2
  23. package/dist/{7913.bundle.4f259b2f5f30efeaf121.js → 9788.bundle.a2f2b48921c47b54853c.js} +18 -24
  24. package/dist/9788.css +3 -0
  25. package/dist/{app.bundle.289e1ee9fe757434bbc4.js → app.bundle.21e229cd4d308d88ddf4.js} +2882 -2782
  26. package/dist/app.bundle.css +2 -2
  27. package/dist/cornerstoneDICOMImageLoader.min.js +1 -1
  28. package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -1
  29. package/dist/index.html +1 -1
  30. package/dist/{polySeg.bundle.0a70e65fe89169ec7508.js → polySeg.bundle.229cf4761a5e2c225748.js} +3 -3
  31. package/dist/{suv-peak-worker.bundle.54d68bc1808ad3d77994.js → suv-peak-worker.bundle.48128a064ed28e623bf8.js} +1 -1
  32. package/dist/sw.js +1 -1
  33. package/package.json +20 -20
  34. package/dist/3482.css +0 -1
  35. /package/dist/{153.bundle.3772df1d5fd3d3f06a0a.js → 153.bundle.3727289b597308e3295f.js} +0 -0
  36. /package/dist/{2791.bundle.2f25931df62e9ebaafb2.js → 2791.bundle.7874c31472a55a7b35a7.js} +0 -0
  37. /package/dist/{4353.bundle.cf8a64e611cb6330f3e5.js → 4353.bundle.e7f048a0002dc3bb19fb.js} +0 -0
  38. /package/dist/{4978.bundle.16df43d1e89383235312.js → 4978.bundle.6474620f33562f3418f8.js} +0 -0
  39. /package/dist/{6591.bundle.78485be183f68b456495.js → 6591.bundle.1c29e66a86ab07463109.js} +0 -0
  40. /package/dist/{7246.bundle.b6c6f4324d62ab2960bb.js → 7246.bundle.cbc90a04abb3d771d7ba.js} +0 -0
  41. /package/dist/{7376.bundle.36672e2cbaafb7fa23b3.js → 7376.bundle.b31063489283cc2c5e16.js} +0 -0
  42. /package/dist/{7502.bundle.58965e833bce0661b63f.js → 7502.bundle.6f9c7545a4ed24158417.js} +0 -0
  43. /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([[7360],{
2
+ (globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[4566],{
3
3
 
4
- /***/ 91851:
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 */ Z: () => (/* binding */ CodeNameCodeSequenceValues)
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
- CornerstoneFreeText: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
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
- /***/ 97360:
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: ../../../node_modules/gl-matrix/esm/index.js + 1 modules
59
- var esm = __webpack_require__(72076);
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 dist_esm = __webpack_require__(93725);
83
+ var esm = __webpack_require__(93725);
62
84
  // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 1 modules
63
- var core_dist_esm = __webpack_require__(12651);
64
- // EXTERNAL MODULE: ../../../extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js
65
- var dicomSRModule = __webpack_require__(75449);
66
- ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/constants/scoordTypes.js
67
- /* harmony default export */ const scoordTypes = ({
68
- POINT: 'POINT',
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
- class DICOMSRDisplayTool extends dist_esm.AnnotationTool {
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
- // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
98
- if (!annotations?.length) {
99
- return;
100
- }
101
- annotations = this.filterInteractableAnnotationsForElement(element, annotations);
102
- if (!annotations?.length) {
103
- return;
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
- const trackingUniqueIdentifiersForElement = (0,dicomSRModule/* getTrackingUniqueIdentifiersForElement */.yR)(element);
106
- const {
107
- activeIndex,
108
- trackingUniqueIdentifiers
109
- } = trackingUniqueIdentifiersForElement;
110
- const activeTrackingUniqueIdentifier = trackingUniqueIdentifiers[activeIndex];
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
- // Filter toolData to only render the data for the active SR.
113
- const filteredAnnotations = annotations.filter(annotation => trackingUniqueIdentifiers.includes(annotation.data?.TrackingUniqueIdentifier));
114
- if (!viewport._actors?.size) {
115
- return;
116
- }
117
- const styleSpecifier = {
118
- toolGroupId: this.toolGroupId,
119
- toolName: this.getToolName(),
120
- viewportId: enabledElement.viewport.id
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
- referencedImageId
134
- } = annotation.metadata;
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
- const labelLength = Math.min(labels.length, 5);
180
- const lines = [];
181
- for (let i = 0; i < labelLength; i++) {
182
- const labelEntry = labels[i];
183
- lines.push(`${_labelToShorthand(labelEntry.label)}: ${labelEntry.value}`);
184
- }
185
- return lines;
186
- }
187
- renderPolyLine(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
188
- const drawingOptions = {
189
- color: options.color,
190
- width: options.lineWidth,
191
- lineDash: options.lineDash
192
- };
193
- let allCanvasCoordinates = [];
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
- allCanvasCoordinates = allCanvasCoordinates.concat(canvasCoordinates);
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
- renderPoint(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
217
- const canvasCoordinates = [];
218
- renderableData.map((data, index) => {
219
- const point = data[0];
220
- // This gives us one point for arrow
221
- canvasCoordinates.push(viewport.worldToCanvas(point));
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
- // Todo: handle drawing probe as probe, currently we are drawing it as an arrow
241
- dist_esm.drawing.drawArrow(svgDrawingHelper, annotationUID, arrowUID, canvasCoordinates[1], canvasCoordinates[0], {
242
- color: options.color,
243
- width: options.lineWidth
244
- });
245
- });
246
- return canvasCoordinates; // used for drawing textBox
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
- renderEllipse(svgDrawingHelper, viewport, renderableData, annotationUID, referencedImageId, options) {
249
- let canvasCoordinates;
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
- * This annotation (DICOMSRDisplay) is only used by the SR viewport.
378
- * This is used before the annotation is hydrated. If hydrated the measurement will be added
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: measurement.TrackingUniqueIdentifier,
272
+ annotationUID: TrackingUniqueIdentifier,
383
273
  highlighted: false,
384
274
  isLocked: false,
385
275
  invalidated: false,
386
276
  metadata: {
387
- toolName: toolName,
388
- FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID,
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
- TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
398
- renderableData: measurementData.renderableData,
399
- frameNumber
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
- // find which axis is parallel to the columnCosines
489
- const columnCosinesVec = esm/* vec3.fromValues */.R3.fromValues(...columnCosines);
490
- const projectedMajorAxisOnColVec = Math.abs(esm/* vec3.dot */.R3.dot(columnCosinesVec, majorAxisVec));
491
- const projectedMinorAxisOnColVec = Math.abs(esm/* vec3.dot */.R3.dot(columnCosinesVec, minorAxisVec));
492
- const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec);
493
- const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec);
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 = cornerstoneAdapters.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
- // EXTERNAL MODULE: ../../../extensions/cornerstone-dicom-sr/src/constants/CodeNameCodeSequenceValues.ts
562
- var CodeNameCodeSequenceValues = __webpack_require__(91851);
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
- CodeScheme: Cornerstone3DCodeScheme
572
- } = adapters_es.adaptersSR.Cornerstone3D;
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
- // TODO ->
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 isImagingMeasurementReport = ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues/* default */.Z.ImagingMeasurementReport;
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
- async function _load(displaySet, servicesManager, extensionManager) {
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
- } = displaySet.instance;
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: displaySet.instance.StudyInstanceUID,
704
- SeriesInstanceUID: displaySet.instance.SeriesInstanceUID,
705
- SOPInstanceUID: displaySet.instance.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 (displaySet.isLoaded !== true) {
513
+ if (srDisplaySet.isLoaded !== true) {
714
514
  await retrieveBulkData(ContentSequence);
715
515
  }
716
- if (displaySet.isImagingMeasurementReport) {
717
- displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
718
- displaySet.measurements = _getMeasurements(ContentSequence);
516
+ if (srDisplaySet.isImagingMeasurementReport) {
517
+ srDisplaySet.referencedImages = _getReferencedImagesList(ContentSequence);
518
+ srDisplaySet.measurements = _getMeasurements(ContentSequence);
719
519
  } else {
720
- displaySet.referencedImages = [];
721
- displaySet.measurements = [];
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
- displaySet.isHydrated = false;
725
- displaySet.isRehydratable = isRehydratable(displaySet, mappings);
726
- displaySet.isLoaded = true;
524
+ srDisplaySet.isHydrated = false;
525
+ srDisplaySet.isRehydratable = isRehydratable(srDisplaySet, mappings);
526
+ srDisplaySet.isLoaded = true;
727
527
 
728
- // Check currently added displaySets and add measurements if the sources exist.
528
+ /** Check currently added displaySets and add measurements if the sources exist */
729
529
  displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
730
- _checkIfCanAddMeasurementsToDisplaySet(displaySet, activeDisplaySet, dataSource, servicesManager);
530
+ _checkIfCanAddMeasurementsToDisplaySet(srDisplaySet, activeDisplaySet, dataSource, servicesManager);
731
531
  });
732
532
 
733
- // Subscribe to new displaySets as the source may come in after.
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
- // If there are still some measurements that have not yet been loaded into cornerstone,
739
- // See if we can load them onto any of the new displaySets.
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(displaySet, newDisplaySet, dataSource, servicesManager);
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
- let unloadedMeasurements = srDisplaySet.measurements.filter(measurement => measurement.loaded === false);
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
- if (!newDisplaySet instanceof ImageSet) {
755
- // This also filters out _this_ displaySet, as it is not an ImageSet.
756
- return;
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 (newDisplaySet.unsupported) {
580
+ if (!unloadedMeasurements?.length) {
759
581
  return;
760
582
  }
761
- const {
762
- sopClassUids
763
- } = newDisplaySet;
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
- // Check if any have the newDisplaySet is the correct SOPClass.
766
- unloadedMeasurements = unloadedMeasurements.filter(measurement => measurement.coords.some(coord => sopClassUids.includes(coord.ReferencedSOPSequence.ReferencedSOPClassUID)));
767
- if (unloadedMeasurements.length === 0) {
768
- // New displaySet isn't the correct SOPClass, so can't contain the referenced images.
769
- return;
770
- }
771
- const SOPInstanceUIDs = [];
772
- unloadedMeasurements.forEach(measurement => {
773
- const {
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
- SOPInstanceUID,
791
- frameNumber
792
- } = metadataProvider.getUIDsFromImageID(imageId);
793
- if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
794
- for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
795
- let measurement = unloadedMeasurements[j];
796
- const onBeforeSRAddMeasurement = customizationService.getModeCustomization('onBeforeSRAddMeasurement')?.value;
797
- if (typeof onBeforeSRAddMeasurement === 'function') {
798
- measurement = onBeforeSRAddMeasurement({
799
- measurement,
800
- StudyInstanceUID: srDisplaySet.StudyInstanceUID,
801
- SeriesInstanceUID: srDisplaySet.SeriesInstanceUID
802
- });
803
- }
804
- if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
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
- // NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
829
- // Standard. But for now, we will support only one ReferenceFrameNumber.
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 === CodeNameCodeSequenceValues/* default */.Z.ImagingMeasurements);
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 === CodeNameCodeSequenceValues/* default */.Z.MeasurementGroup);
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 === CodeNameCodeSequenceValues/* default */.Z.TrackingUniqueIdentifier);
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 !== CodeNameCodeSequenceValues/* default */.Z.TrackingUniqueIdentifier) {
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 === CodeNameCodeSequenceValues/* default */.Z.TrackingIdentifier);
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 === CodeNameCodeSequenceValues/* default */.Z.TrackingIdentifier);
938
- const finding = mergedContentSequence.find(item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues/* default */.Z.Finding);
939
- const findingSites = mergedContentSequence.filter(item => item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SRT && item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues/* default */.Z.FindingSite);
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 === CodeNameCodeSequenceValues/* default */.Z.CornerstoneFreeText) {
819
+ if (finding && enums/* CodingSchemeDesignators */.Ql.CornerstoneCodeSchemes.includes(finding.ConceptCodeSequence.CodingSchemeDesignator) && finding.ConceptCodeSequence.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT) {
948
820
  measurement.labels.push({
949
- label: CORNERSTONE_FREETEXT_CODE_VALUE,
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 === CodeNameCodeSequenceValues/* default */.Z.CornerstoneFreeText);
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: CORNERSTONE_FREETEXT_CODE_VALUE,
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
- function _getCoordsFromSCOORDOrSCOORD3D(item) {
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
- } = item;
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
- // ContentSequence has length of 1 as RelationshipType === 'INFERRED FROM'
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 === CodeNameCodeSequenceValues/* default */.Z.ImageLibrary);
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 === CodeNameCodeSequenceValues/* default */.Z.ImageLibraryGroup);
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 measurement = Object.assign({}, annotation, {
1203
- finding,
1204
- findingSites
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
- toolData.push(measurement);
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
- const uidFilter = measurementData.map(md => md.uid);
1209
- const uids = uidFilter.slice();
1210
- const annotationManager = dist_esm.annotation.state.getAnnotationManager();
1211
- const framesOfReference = annotationManager.getFramesOfReference();
1212
- for (let i = 0; i < framesOfReference.length; i++) {
1213
- const frameOfReference = framesOfReference[i];
1214
- const frameOfReferenceAnnotations = annotationManager.getAnnotations(frameOfReference);
1215
- const toolTypes = Object.keys(frameOfReferenceAnnotations);
1216
- for (let j = 0; j < toolTypes.length; j++) {
1217
- const toolType = toolTypes[j];
1218
- const annotations = frameOfReferenceAnnotations[toolType];
1219
- if (annotations) {
1220
- for (let k = 0; k < annotations.length; k++) {
1221
- const annotation = annotations[k];
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
- /* harmony default export */ const utils_getFilteredCornerstoneToolState = (getFilteredCornerstoneToolState);
1237
- ;// CONCATENATED MODULE: ../../../extensions/cornerstone-dicom-sr/src/commandsModule.ts
1238
-
1239
-
1240
-
1241
-
1242
-
1243
- const {
1244
- MeasurementReport
1245
- } = adapters_es.adaptersSR.Cornerstone3D;
1246
- const {
1247
- log: commandsModule_log
1248
- } = src["default"];
1249
-
1250
- /**
1251
- *
1252
- * @param measurementData An array of measurements from the measurements service
1253
- * that you wish to serialize.
1254
- * @param additionalFindingTypes toolTypes that should be stored with labels as Findings
1255
- * @param options Naturalized DICOM JSON headers to merge into the displaySet.
1256
- *
1257
- */
1258
- const _generateReport = (measurementData, additionalFindingTypes, options = {}) => {
1259
- const filteredToolState = utils_getFilteredCornerstoneToolState(measurementData, additionalFindingTypes);
1260
- const report = MeasurementReport.generateReport(filteredToolState, core_dist_esm.metaData, core_dist_esm.utilities.worldToImageCoords, options);
1261
- const {
1262
- dataset
1263
- } = report;
1264
-
1265
- // Set the default character set as UTF-8
1266
- // https://dicom.innolitics.com/ciods/nm-image/sop-common/00080005
1267
- if (typeof dataset.SpecificCharacterSet === 'undefined') {
1268
- dataset.SpecificCharacterSet = 'ISO_IR 192';
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
- const commandsModule = props => {
1726
+ function getDisplayText(annotation) {
1273
1727
  const {
1274
- servicesManager
1275
- } = props;
1728
+ data
1729
+ } = annotation;
1730
+ if (!data) {
1731
+ return [''];
1732
+ }
1276
1733
  const {
1277
- customizationService
1278
- } = servicesManager.services;
1279
- const actions = {
1280
- /**
1281
- *
1282
- * @param measurementData An array of measurements from the measurements service
1283
- * @param additionalFindingTypes toolTypes that should be stored with labels as Findings
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
- actions,
1370
- definitions,
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,dist_esm.addTool)(InstanceClass);
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, dist_esm.LengthTool);
1396
- addToolInstance(tools_toolNames.SRBidirectional, dist_esm.BidirectionalTool);
1397
- addToolInstance(tools_toolNames.SREllipticalROI, dist_esm.EllipticalROITool);
1398
- addToolInstance(tools_toolNames.SRCircleROI, dist_esm.CircleROITool);
1399
- addToolInstance(tools_toolNames.SRArrowAnnotate, dist_esm.ArrowAnnotateTool);
1400
- addToolInstance(tools_toolNames.SRAngle, dist_esm.AngleTool);
1401
- addToolInstance(tools_toolNames.SRPlanarFreehandROI, dist_esm.PlanarFreehandROITool);
1402
- addToolInstance(tools_toolNames.SRRectangleROI, dist_esm.RectangleROITool);
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, dist_esm.CobbAngleTool);
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
- dist_esm.annotation.config.style.setToolGroupToolStyles('SRToolGroup', {
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 === 'CORNERSTONEFREETEXT');
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 === 'CORNERSTONEFREETEXT') {
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 CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
1652
- const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
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') {