@cornerstonejs/adapters 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@cornerstonejs/adapters.es.js +1673 -2037
- package/dist/@cornerstonejs/adapters.es.js.map +1 -1
- package/dist/@cornerstonejs/dts/index.d.ts +3 -0
- package/package.json +9 -32
- package/src/{index.js → index.ts} +0 -0
- package/dist/@cornerstonejs/adapters.js +0 -30070
- package/dist/@cornerstonejs/adapters.js.map +0 -1
- package/src/adapters/Cornerstone/Angle.js +0 -92
- package/src/adapters/Cornerstone/ArrowAnnotate.js +0 -106
- package/src/adapters/Cornerstone/Bidirectional.js +0 -187
- package/src/adapters/Cornerstone/CircleRoi.js +0 -109
- package/src/adapters/Cornerstone/CobbAngle.js +0 -96
- package/src/adapters/Cornerstone/EllipticalRoi.js +0 -149
- package/src/adapters/Cornerstone/FreehandRoi.js +0 -82
- package/src/adapters/Cornerstone/Length.js +0 -80
- package/src/adapters/Cornerstone/MeasurementReport.js +0 -352
- package/src/adapters/Cornerstone/RectangleRoi.js +0 -92
- package/src/adapters/Cornerstone/Segmentation.js +0 -118
- package/src/adapters/Cornerstone/Segmentation_3X.js +0 -632
- package/src/adapters/Cornerstone/Segmentation_4X.js +0 -1543
- package/src/adapters/Cornerstone/cornerstone4Tag.js +0 -1
- package/src/adapters/Cornerstone/index.js +0 -27
- package/src/adapters/Cornerstone3D/ArrowAnnotate.js +0 -155
- package/src/adapters/Cornerstone3D/Bidirectional.js +0 -196
- package/src/adapters/Cornerstone3D/CodingScheme.js +0 -16
- package/src/adapters/Cornerstone3D/EllipticalROI.js +0 -204
- package/src/adapters/Cornerstone3D/Length.js +0 -113
- package/src/adapters/Cornerstone3D/MeasurementReport.js +0 -445
- package/src/adapters/Cornerstone3D/PlanarFreehandROI.js +0 -137
- package/src/adapters/Cornerstone3D/Probe.js +0 -106
- package/src/adapters/Cornerstone3D/cornerstone3DTag.js +0 -1
- package/src/adapters/Cornerstone3D/index.js +0 -24
- package/src/adapters/VTKjs/Segmentation.js +0 -223
- package/src/adapters/VTKjs/index.js +0 -7
- package/src/adapters/helpers.js +0 -19
- package/src/adapters/index.js +0 -11
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default "cornerstoneTools@^4.0.0";
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import MeasurementReport from "./MeasurementReport";
|
|
2
|
-
import Length from "./Length";
|
|
3
|
-
import FreehandRoi from "./FreehandRoi";
|
|
4
|
-
import Bidirectional from "./Bidirectional";
|
|
5
|
-
import EllipticalRoi from "./EllipticalRoi";
|
|
6
|
-
import CircleRoi from "./CircleRoi";
|
|
7
|
-
import ArrowAnnotate from "./ArrowAnnotate";
|
|
8
|
-
import Segmentation from "./Segmentation";
|
|
9
|
-
import CobbAngle from "./CobbAngle";
|
|
10
|
-
import Angle from "./Angle";
|
|
11
|
-
import RectangleRoi from "./RectangleRoi";
|
|
12
|
-
|
|
13
|
-
const Cornerstone = {
|
|
14
|
-
Length,
|
|
15
|
-
FreehandRoi,
|
|
16
|
-
Bidirectional,
|
|
17
|
-
EllipticalRoi,
|
|
18
|
-
CircleRoi,
|
|
19
|
-
ArrowAnnotate,
|
|
20
|
-
MeasurementReport,
|
|
21
|
-
Segmentation,
|
|
22
|
-
CobbAngle,
|
|
23
|
-
Angle,
|
|
24
|
-
RectangleRoi
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export default Cornerstone;
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import MeasurementReport from "./MeasurementReport";
|
|
2
|
-
import { utilities } from "dcmjs";
|
|
3
|
-
import CORNERSTONE_3D_TAG from "./cornerstone3DTag";
|
|
4
|
-
import CodingScheme from "./CodingScheme";
|
|
5
|
-
|
|
6
|
-
const { Point: TID300Point } = utilities.TID300;
|
|
7
|
-
|
|
8
|
-
const ARROW_ANNOTATE = "ArrowAnnotate";
|
|
9
|
-
const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${ARROW_ANNOTATE}`;
|
|
10
|
-
|
|
11
|
-
const { codeValues, CodingSchemeDesignator } = CodingScheme;
|
|
12
|
-
|
|
13
|
-
class ArrowAnnotate {
|
|
14
|
-
static getMeasurementData(
|
|
15
|
-
MeasurementGroup,
|
|
16
|
-
sopInstanceUIDToImageIdMap,
|
|
17
|
-
imageToWorldCoords,
|
|
18
|
-
metadata
|
|
19
|
-
) {
|
|
20
|
-
const { defaultState, SCOORDGroup, ReferencedFrameNumber } =
|
|
21
|
-
MeasurementReport.getSetupMeasurementData(
|
|
22
|
-
MeasurementGroup,
|
|
23
|
-
sopInstanceUIDToImageIdMap,
|
|
24
|
-
metadata,
|
|
25
|
-
ArrowAnnotate.toolType
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const referencedImageId =
|
|
29
|
-
defaultState.annotation.metadata.referencedImageId;
|
|
30
|
-
|
|
31
|
-
const text = defaultState.annotation.metadata.label;
|
|
32
|
-
|
|
33
|
-
const { GraphicData } = SCOORDGroup;
|
|
34
|
-
|
|
35
|
-
const worldCoords = [];
|
|
36
|
-
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
37
|
-
const point = imageToWorldCoords(referencedImageId, [
|
|
38
|
-
GraphicData[i],
|
|
39
|
-
GraphicData[i + 1]
|
|
40
|
-
]);
|
|
41
|
-
worldCoords.push(point);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Since the arrowAnnotate measurement is just a point, to generate the tool state
|
|
45
|
-
// we derive the second point based on the image size relative to the first point.
|
|
46
|
-
if (worldCoords.length === 1) {
|
|
47
|
-
const imagePixelModule = metadata.get(
|
|
48
|
-
"imagePixelModule",
|
|
49
|
-
referencedImageId
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
let xOffset = 10;
|
|
53
|
-
let yOffset = 10;
|
|
54
|
-
|
|
55
|
-
if (imagePixelModule) {
|
|
56
|
-
const { columns, rows } = imagePixelModule;
|
|
57
|
-
xOffset = columns / 10;
|
|
58
|
-
yOffset = rows / 10;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const secondPoint = imageToWorldCoords(referencedImageId, [
|
|
62
|
-
GraphicData[0] + xOffset,
|
|
63
|
-
GraphicData[1] + yOffset
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
worldCoords.push(secondPoint);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const state = defaultState;
|
|
70
|
-
|
|
71
|
-
state.annotation.data = {
|
|
72
|
-
text,
|
|
73
|
-
handles: {
|
|
74
|
-
arrowFirst: true,
|
|
75
|
-
points: [worldCoords[0], worldCoords[1]],
|
|
76
|
-
activeHandleIndex: 0,
|
|
77
|
-
textBox: {
|
|
78
|
-
hasMoved: false
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
frameNumber: ReferencedFrameNumber
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return state;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
static getTID300RepresentationArguments(tool, worldToImageCoords) {
|
|
88
|
-
const { data, metadata } = tool;
|
|
89
|
-
let { finding, findingSites } = tool;
|
|
90
|
-
const { referencedImageId } = metadata;
|
|
91
|
-
|
|
92
|
-
if (!referencedImageId) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
"ArrowAnnotate.getTID300RepresentationArguments: referencedImageId is not defined"
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const { points, arrowFirst } = data.handles;
|
|
99
|
-
|
|
100
|
-
let point;
|
|
101
|
-
|
|
102
|
-
if (arrowFirst) {
|
|
103
|
-
point = points[0];
|
|
104
|
-
} else {
|
|
105
|
-
point = points[1];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const pointImage = worldToImageCoords(referencedImageId, point);
|
|
109
|
-
|
|
110
|
-
const TID300RepresentationArguments = {
|
|
111
|
-
points: [
|
|
112
|
-
{
|
|
113
|
-
x: pointImage[0],
|
|
114
|
-
y: pointImage[1]
|
|
115
|
-
}
|
|
116
|
-
],
|
|
117
|
-
trackingIdentifierTextValue,
|
|
118
|
-
findingSites: findingSites || []
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// If freetext finding isn't present, add it from the tool text.
|
|
122
|
-
if (!finding || finding.CodeValue !== codeValues.CORNERSTONEFREETEXT) {
|
|
123
|
-
finding = {
|
|
124
|
-
CodeValue: codeValues.CORNERSTONEFREETEXT,
|
|
125
|
-
CodingSchemeDesignator,
|
|
126
|
-
CodeMeaning: data.text
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
TID300RepresentationArguments.finding = finding;
|
|
131
|
-
|
|
132
|
-
return TID300RepresentationArguments;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
ArrowAnnotate.toolType = ARROW_ANNOTATE;
|
|
137
|
-
ArrowAnnotate.utilityToolType = ARROW_ANNOTATE;
|
|
138
|
-
ArrowAnnotate.TID300Representation = TID300Point;
|
|
139
|
-
ArrowAnnotate.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
|
|
140
|
-
if (!TrackingIdentifier.includes(":")) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":");
|
|
145
|
-
|
|
146
|
-
if (cornerstone3DTag !== CORNERSTONE_3D_TAG) {
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return toolType === ARROW_ANNOTATE;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
MeasurementReport.registerTool(ArrowAnnotate);
|
|
154
|
-
|
|
155
|
-
export default ArrowAnnotate;
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { utilities } from "dcmjs";
|
|
2
|
-
import CORNERSTONE_3D_TAG from "./cornerstone3DTag";
|
|
3
|
-
import MeasurementReport from "./MeasurementReport";
|
|
4
|
-
import { toArray } from "../helpers";
|
|
5
|
-
|
|
6
|
-
const { Bidirectional: TID300Bidirectional } = utilities.TID300;
|
|
7
|
-
|
|
8
|
-
const BIDIRECTIONAL = "Bidirectional";
|
|
9
|
-
const LONG_AXIS = "Long Axis";
|
|
10
|
-
const SHORT_AXIS = "Short Axis";
|
|
11
|
-
const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${BIDIRECTIONAL}`;
|
|
12
|
-
|
|
13
|
-
class Bidirectional {
|
|
14
|
-
static getMeasurementData(
|
|
15
|
-
MeasurementGroup,
|
|
16
|
-
sopInstanceUIDToImageIdMap,
|
|
17
|
-
imageToWorldCoords,
|
|
18
|
-
metadata
|
|
19
|
-
) {
|
|
20
|
-
const { defaultState, ReferencedFrameNumber } =
|
|
21
|
-
MeasurementReport.getSetupMeasurementData(
|
|
22
|
-
MeasurementGroup,
|
|
23
|
-
sopInstanceUIDToImageIdMap,
|
|
24
|
-
metadata,
|
|
25
|
-
Bidirectional.toolType
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const referencedImageId =
|
|
29
|
-
defaultState.annotation.metadata.referencedImageId;
|
|
30
|
-
const { ContentSequence } = MeasurementGroup;
|
|
31
|
-
|
|
32
|
-
const longAxisNUMGroup = toArray(ContentSequence).find(
|
|
33
|
-
group => group.ConceptNameCodeSequence.CodeMeaning === LONG_AXIS
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const longAxisSCOORDGroup = toArray(
|
|
37
|
-
longAxisNUMGroup.ContentSequence
|
|
38
|
-
).find(group => group.ValueType === "SCOORD");
|
|
39
|
-
|
|
40
|
-
const shortAxisNUMGroup = toArray(ContentSequence).find(
|
|
41
|
-
group => group.ConceptNameCodeSequence.CodeMeaning === SHORT_AXIS
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const shortAxisSCOORDGroup = toArray(
|
|
45
|
-
shortAxisNUMGroup.ContentSequence
|
|
46
|
-
).find(group => group.ValueType === "SCOORD");
|
|
47
|
-
|
|
48
|
-
const worldCoords = [];
|
|
49
|
-
|
|
50
|
-
[longAxisSCOORDGroup, shortAxisSCOORDGroup].forEach(group => {
|
|
51
|
-
const { GraphicData } = group;
|
|
52
|
-
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
53
|
-
const point = imageToWorldCoords(referencedImageId, [
|
|
54
|
-
GraphicData[i],
|
|
55
|
-
GraphicData[i + 1]
|
|
56
|
-
]);
|
|
57
|
-
worldCoords.push(point);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const state = defaultState;
|
|
62
|
-
|
|
63
|
-
state.annotation.data = {
|
|
64
|
-
handles: {
|
|
65
|
-
points: [
|
|
66
|
-
worldCoords[0],
|
|
67
|
-
worldCoords[1],
|
|
68
|
-
worldCoords[2],
|
|
69
|
-
worldCoords[3]
|
|
70
|
-
],
|
|
71
|
-
activeHandleIndex: 0,
|
|
72
|
-
textBox: {
|
|
73
|
-
hasMoved: false
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
cachedStats: {
|
|
77
|
-
[`imageId:${referencedImageId}`]: {
|
|
78
|
-
length: longAxisNUMGroup.MeasuredValueSequence.NumericValue,
|
|
79
|
-
width: shortAxisNUMGroup.MeasuredValueSequence.NumericValue
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
frameNumber: ReferencedFrameNumber
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
return state;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
static getTID300RepresentationArguments(tool, worldToImageCoords) {
|
|
89
|
-
const { data, finding, findingSites, metadata } = tool;
|
|
90
|
-
const { cachedStats = {}, handles } = data;
|
|
91
|
-
|
|
92
|
-
const { referencedImageId } = metadata;
|
|
93
|
-
|
|
94
|
-
if (!referencedImageId) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
"Bidirectional.getTID300RepresentationArguments: referencedImageId is not defined"
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const { length, width } =
|
|
101
|
-
cachedStats[`imageId:${referencedImageId}`] || {};
|
|
102
|
-
const { points } = handles;
|
|
103
|
-
|
|
104
|
-
// Find the length and width point pairs by comparing the distances of the points at 0,1 to points at 2,3
|
|
105
|
-
let firstPointPairs = [points[0], points[1]];
|
|
106
|
-
let secondPointPairs = [points[2], points[3]];
|
|
107
|
-
|
|
108
|
-
let firstPointPairsDistance = Math.sqrt(
|
|
109
|
-
Math.pow(firstPointPairs[0][0] - firstPointPairs[1][0], 2) +
|
|
110
|
-
Math.pow(firstPointPairs[0][1] - firstPointPairs[1][1], 2) +
|
|
111
|
-
Math.pow(firstPointPairs[0][2] - firstPointPairs[1][2], 2)
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
let secondPointPairsDistance = Math.sqrt(
|
|
115
|
-
Math.pow(secondPointPairs[0][0] - secondPointPairs[1][0], 2) +
|
|
116
|
-
Math.pow(secondPointPairs[0][1] - secondPointPairs[1][1], 2) +
|
|
117
|
-
Math.pow(secondPointPairs[0][2] - secondPointPairs[1][2], 2)
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
let shortAxisPoints;
|
|
121
|
-
let longAxisPoints;
|
|
122
|
-
if (firstPointPairsDistance > secondPointPairsDistance) {
|
|
123
|
-
shortAxisPoints = firstPointPairs;
|
|
124
|
-
longAxisPoints = secondPointPairs;
|
|
125
|
-
} else {
|
|
126
|
-
shortAxisPoints = secondPointPairs;
|
|
127
|
-
longAxisPoints = firstPointPairs;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const longAxisStartImage = worldToImageCoords(
|
|
131
|
-
referencedImageId,
|
|
132
|
-
shortAxisPoints[0]
|
|
133
|
-
);
|
|
134
|
-
const longAxisEndImage = worldToImageCoords(
|
|
135
|
-
referencedImageId,
|
|
136
|
-
shortAxisPoints[1]
|
|
137
|
-
);
|
|
138
|
-
const shortAxisStartImage = worldToImageCoords(
|
|
139
|
-
referencedImageId,
|
|
140
|
-
longAxisPoints[0]
|
|
141
|
-
);
|
|
142
|
-
const shortAxisEndImage = worldToImageCoords(
|
|
143
|
-
referencedImageId,
|
|
144
|
-
longAxisPoints[1]
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
longAxis: {
|
|
149
|
-
point1: {
|
|
150
|
-
x: longAxisStartImage[0],
|
|
151
|
-
y: longAxisStartImage[1]
|
|
152
|
-
},
|
|
153
|
-
point2: {
|
|
154
|
-
x: longAxisEndImage[0],
|
|
155
|
-
y: longAxisEndImage[1]
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
shortAxis: {
|
|
159
|
-
point1: {
|
|
160
|
-
x: shortAxisStartImage[0],
|
|
161
|
-
y: shortAxisStartImage[1]
|
|
162
|
-
},
|
|
163
|
-
point2: {
|
|
164
|
-
x: shortAxisEndImage[0],
|
|
165
|
-
y: shortAxisEndImage[1]
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
longAxisLength: length,
|
|
169
|
-
shortAxisLength: width,
|
|
170
|
-
trackingIdentifierTextValue,
|
|
171
|
-
finding: finding,
|
|
172
|
-
findingSites: findingSites || []
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
Bidirectional.toolType = BIDIRECTIONAL;
|
|
178
|
-
Bidirectional.utilityToolType = BIDIRECTIONAL;
|
|
179
|
-
Bidirectional.TID300Representation = TID300Bidirectional;
|
|
180
|
-
Bidirectional.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
|
|
181
|
-
if (!TrackingIdentifier.includes(":")) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":");
|
|
186
|
-
|
|
187
|
-
if (cornerstone3DTag !== CORNERSTONE_3D_TAG) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return toolType === BIDIRECTIONAL;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
MeasurementReport.registerTool(Bidirectional);
|
|
195
|
-
|
|
196
|
-
export default Bidirectional;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// This is a custom coding scheme defined to store some annotations from Cornerstone.
|
|
2
|
-
// Note: CodeMeaning is VR type LO, which means we only actually support 64 characters
|
|
3
|
-
// here this is fine for most labels, but may be problematic at some point.
|
|
4
|
-
const CORNERSTONEFREETEXT = "CORNERSTONEFREETEXT";
|
|
5
|
-
|
|
6
|
-
// Cornerstone specified coding scheme for storing findings
|
|
7
|
-
const CodingSchemeDesignator = "CORNERSTONEJS";
|
|
8
|
-
|
|
9
|
-
const CodingScheme = {
|
|
10
|
-
CodingSchemeDesignator,
|
|
11
|
-
codeValues: {
|
|
12
|
-
CORNERSTONEFREETEXT
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export default CodingScheme;
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { vec3 } from "gl-matrix";
|
|
2
|
-
import { utilities } from "dcmjs";
|
|
3
|
-
import CORNERSTONE_3D_TAG from "./cornerstone3DTag";
|
|
4
|
-
import MeasurementReport from "./MeasurementReport";
|
|
5
|
-
|
|
6
|
-
const { Ellipse: TID300Ellipse } = utilities.TID300;
|
|
7
|
-
|
|
8
|
-
const ELLIPTICALROI = "EllipticalROI";
|
|
9
|
-
const EPSILON = 1e-4;
|
|
10
|
-
|
|
11
|
-
const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${ELLIPTICALROI}`;
|
|
12
|
-
|
|
13
|
-
class EllipticalROI {
|
|
14
|
-
static getMeasurementData(
|
|
15
|
-
MeasurementGroup,
|
|
16
|
-
sopInstanceUIDToImageIdMap,
|
|
17
|
-
imageToWorldCoords,
|
|
18
|
-
metadata
|
|
19
|
-
) {
|
|
20
|
-
const { defaultState, NUMGroup, SCOORDGroup, ReferencedFrameNumber } =
|
|
21
|
-
MeasurementReport.getSetupMeasurementData(
|
|
22
|
-
MeasurementGroup,
|
|
23
|
-
sopInstanceUIDToImageIdMap,
|
|
24
|
-
metadata,
|
|
25
|
-
EllipticalROI.toolType
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const referencedImageId =
|
|
29
|
-
defaultState.annotation.metadata.referencedImageId;
|
|
30
|
-
|
|
31
|
-
const { GraphicData } = SCOORDGroup;
|
|
32
|
-
|
|
33
|
-
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
|
34
|
-
// But Cornerstone3D points are ordered as top, bottom, left, right for the
|
|
35
|
-
// ellipse so we need to identify if the majorAxis is horizontal or vertical
|
|
36
|
-
// in the image plane and then choose the correct points to use for the ellipse.
|
|
37
|
-
const pointsWorld = [];
|
|
38
|
-
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
39
|
-
const worldPos = imageToWorldCoords(referencedImageId, [
|
|
40
|
-
GraphicData[i],
|
|
41
|
-
GraphicData[i + 1]
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
pointsWorld.push(worldPos);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const majorAxisStart = vec3.fromValues(...pointsWorld[0]);
|
|
48
|
-
const majorAxisEnd = vec3.fromValues(...pointsWorld[1]);
|
|
49
|
-
const minorAxisStart = vec3.fromValues(...pointsWorld[2]);
|
|
50
|
-
const minorAxisEnd = vec3.fromValues(...pointsWorld[3]);
|
|
51
|
-
|
|
52
|
-
const majorAxisVec = vec3.create();
|
|
53
|
-
vec3.sub(majorAxisVec, majorAxisEnd, majorAxisStart);
|
|
54
|
-
|
|
55
|
-
// normalize majorAxisVec to avoid scaling issues
|
|
56
|
-
vec3.normalize(majorAxisVec, majorAxisVec);
|
|
57
|
-
|
|
58
|
-
const minorAxisVec = vec3.create();
|
|
59
|
-
vec3.sub(minorAxisVec, minorAxisEnd, minorAxisStart);
|
|
60
|
-
vec3.normalize(minorAxisVec, minorAxisVec);
|
|
61
|
-
|
|
62
|
-
const imagePlaneModule = metadata.get(
|
|
63
|
-
"imagePlaneModule",
|
|
64
|
-
referencedImageId
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
if (!imagePlaneModule) {
|
|
68
|
-
throw new Error("imageId does not have imagePlaneModule metadata");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const { columnCosines } = imagePlaneModule;
|
|
72
|
-
|
|
73
|
-
// find which axis is parallel to the columnCosines
|
|
74
|
-
const columnCosinesVec = vec3.fromValues(...columnCosines);
|
|
75
|
-
|
|
76
|
-
const projectedMajorAxisOnColVec = vec3.dot(
|
|
77
|
-
columnCosinesVec,
|
|
78
|
-
majorAxisVec
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const projectedMinorAxisOnColVec = vec3.dot(
|
|
82
|
-
columnCosinesVec,
|
|
83
|
-
minorAxisVec
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec);
|
|
87
|
-
const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec);
|
|
88
|
-
|
|
89
|
-
let ellipsePoints = [];
|
|
90
|
-
if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) {
|
|
91
|
-
ellipsePoints = [
|
|
92
|
-
pointsWorld[0],
|
|
93
|
-
pointsWorld[1],
|
|
94
|
-
pointsWorld[2],
|
|
95
|
-
pointsWorld[3]
|
|
96
|
-
];
|
|
97
|
-
} else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) {
|
|
98
|
-
ellipsePoints = [
|
|
99
|
-
pointsWorld[2],
|
|
100
|
-
pointsWorld[3],
|
|
101
|
-
pointsWorld[0],
|
|
102
|
-
pointsWorld[1]
|
|
103
|
-
];
|
|
104
|
-
} else {
|
|
105
|
-
console.warn("OBLIQUE ELLIPSE NOT YET SUPPORTED");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const state = defaultState;
|
|
109
|
-
|
|
110
|
-
state.annotation.data = {
|
|
111
|
-
handles: {
|
|
112
|
-
points: [...ellipsePoints],
|
|
113
|
-
activeHandleIndex: 0,
|
|
114
|
-
textBox: {
|
|
115
|
-
hasMoved: false
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
cachedStats: {
|
|
119
|
-
[`imageId:${referencedImageId}`]: {
|
|
120
|
-
area: NUMGroup
|
|
121
|
-
? NUMGroup.MeasuredValueSequence.NumericValue
|
|
122
|
-
: 0
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
frameNumber: ReferencedFrameNumber
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
return state;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
static getTID300RepresentationArguments(tool, worldToImageCoords) {
|
|
132
|
-
const { data, finding, findingSites, metadata } = tool;
|
|
133
|
-
const { cachedStats = {}, handles } = data;
|
|
134
|
-
|
|
135
|
-
const { referencedImageId } = metadata;
|
|
136
|
-
|
|
137
|
-
if (!referencedImageId) {
|
|
138
|
-
throw new Error(
|
|
139
|
-
"EllipticalROI.getTID300RepresentationArguments: referencedImageId is not defined"
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const top = worldToImageCoords(referencedImageId, handles.points[0]);
|
|
144
|
-
const bottom = worldToImageCoords(referencedImageId, handles.points[1]);
|
|
145
|
-
const left = worldToImageCoords(referencedImageId, handles.points[2]);
|
|
146
|
-
const right = worldToImageCoords(referencedImageId, handles.points[3]);
|
|
147
|
-
|
|
148
|
-
// find the major axis and minor axis
|
|
149
|
-
const topBottomLength = Math.abs(top[1] - bottom[1]);
|
|
150
|
-
const leftRightLength = Math.abs(left[0] - right[0]);
|
|
151
|
-
|
|
152
|
-
let points = [];
|
|
153
|
-
if (topBottomLength > leftRightLength) {
|
|
154
|
-
// major axis is bottom to top
|
|
155
|
-
points.push({ x: top[0], y: top[1] });
|
|
156
|
-
points.push({ x: bottom[0], y: bottom[1] });
|
|
157
|
-
|
|
158
|
-
// minor axis is left to right
|
|
159
|
-
points.push({ x: left[0], y: left[1] });
|
|
160
|
-
points.push({ x: right[0], y: right[1] });
|
|
161
|
-
} else {
|
|
162
|
-
// major axis is left to right
|
|
163
|
-
points.push({ x: left[0], y: left[1] });
|
|
164
|
-
points.push({ x: right[0], y: right[1] });
|
|
165
|
-
|
|
166
|
-
// minor axis is bottom to top
|
|
167
|
-
points.push({ x: top[0], y: top[1] });
|
|
168
|
-
points.push({ x: bottom[0], y: bottom[1] });
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const { area } = cachedStats[`imageId:${referencedImageId}`] || {};
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
area,
|
|
175
|
-
points,
|
|
176
|
-
trackingIdentifierTextValue,
|
|
177
|
-
finding,
|
|
178
|
-
findingSites: findingSites || []
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
EllipticalROI.toolType = ELLIPTICALROI;
|
|
184
|
-
EllipticalROI.utilityToolType = ELLIPTICALROI;
|
|
185
|
-
EllipticalROI.TID300Representation = TID300Ellipse;
|
|
186
|
-
EllipticalROI.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
|
|
187
|
-
if (!TrackingIdentifier.includes(":")) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":");
|
|
192
|
-
|
|
193
|
-
if (cornerstone3DTag !== CORNERSTONE_3D_TAG) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// The following is needed since the new cornerstone3D has changed
|
|
198
|
-
// the EllipticalRoi toolName (which was in the old cornerstone) to EllipticalROI
|
|
199
|
-
return toolType.toLowerCase() === ELLIPTICALROI.toLowerCase();
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
MeasurementReport.registerTool(EllipticalROI);
|
|
203
|
-
|
|
204
|
-
export default EllipticalROI;
|