@cornerstonejs/adapters 0.2.0 → 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 +1603 -2092
- 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 -30195
- 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/CobbAngle.js +0 -125
- 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 -26
- 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,149 +0,0 @@
|
|
|
1
|
-
import { utilities } from "dcmjs";
|
|
2
|
-
import MeasurementReport from "./MeasurementReport";
|
|
3
|
-
import CORNERSTONE_4_TAG from "./cornerstone4Tag";
|
|
4
|
-
|
|
5
|
-
const { Ellipse: TID300Ellipse } = utilities.TID300;
|
|
6
|
-
|
|
7
|
-
const ELLIPTICALROI = "EllipticalRoi";
|
|
8
|
-
|
|
9
|
-
class EllipticalRoi {
|
|
10
|
-
// TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport.
|
|
11
|
-
static getMeasurementData(MeasurementGroup) {
|
|
12
|
-
const { defaultState, NUMGroup, SCOORDGroup } =
|
|
13
|
-
MeasurementReport.getSetupMeasurementData(MeasurementGroup);
|
|
14
|
-
|
|
15
|
-
const { GraphicData } = SCOORDGroup;
|
|
16
|
-
|
|
17
|
-
const majorAxis = [
|
|
18
|
-
{ x: GraphicData[0], y: GraphicData[1] },
|
|
19
|
-
{ x: GraphicData[2], y: GraphicData[3] }
|
|
20
|
-
];
|
|
21
|
-
const minorAxis = [
|
|
22
|
-
{ x: GraphicData[4], y: GraphicData[5] },
|
|
23
|
-
{ x: GraphicData[6], y: GraphicData[7] }
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
// Calculate two opposite corners of box defined by two axes.
|
|
27
|
-
|
|
28
|
-
const minorAxisLength = Math.sqrt(
|
|
29
|
-
Math.pow(minorAxis[0].x - minorAxis[1].x, 2) +
|
|
30
|
-
Math.pow(minorAxis[0].y - minorAxis[1].y, 2)
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
const minorAxisDirection = {
|
|
34
|
-
x: (minorAxis[1].x - minorAxis[0].x) / minorAxisLength,
|
|
35
|
-
y: (minorAxis[1].y - minorAxis[0].y) / minorAxisLength
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const halfMinorAxisLength = minorAxisLength / 2;
|
|
39
|
-
|
|
40
|
-
// First end point of major axis + half minor axis vector
|
|
41
|
-
const corner1 = {
|
|
42
|
-
x: majorAxis[0].x + minorAxisDirection.x * halfMinorAxisLength,
|
|
43
|
-
y: majorAxis[0].y + minorAxisDirection.y * halfMinorAxisLength
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Second end point of major axis - half of minor axis vector
|
|
47
|
-
const corner2 = {
|
|
48
|
-
x: majorAxis[1].x - minorAxisDirection.x * halfMinorAxisLength,
|
|
49
|
-
y: majorAxis[1].y - minorAxisDirection.y * halfMinorAxisLength
|
|
50
|
-
};
|
|
51
|
-
const state = {
|
|
52
|
-
...defaultState,
|
|
53
|
-
toolType: EllipticalRoi.toolType,
|
|
54
|
-
active: false,
|
|
55
|
-
cachedStats: {
|
|
56
|
-
area: NUMGroup ? NUMGroup.MeasuredValueSequence.NumericValue : 0
|
|
57
|
-
},
|
|
58
|
-
handles: {
|
|
59
|
-
end: {
|
|
60
|
-
x: corner1.x,
|
|
61
|
-
y: corner1.y,
|
|
62
|
-
highlight: false,
|
|
63
|
-
active: false
|
|
64
|
-
},
|
|
65
|
-
initialRotation: 0,
|
|
66
|
-
start: {
|
|
67
|
-
x: corner2.x,
|
|
68
|
-
y: corner2.y,
|
|
69
|
-
highlight: false,
|
|
70
|
-
active: false
|
|
71
|
-
},
|
|
72
|
-
textBox: {
|
|
73
|
-
hasMoved: false,
|
|
74
|
-
movesIndependently: false,
|
|
75
|
-
drawnIndependently: true,
|
|
76
|
-
allowedOutsideImage: true,
|
|
77
|
-
hasBoundingBox: true
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
invalidated: true,
|
|
81
|
-
visible: true
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return state;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
static getTID300RepresentationArguments(tool) {
|
|
88
|
-
const { cachedStats = {}, handles, finding, findingSites } = tool;
|
|
89
|
-
const { start, end } = handles;
|
|
90
|
-
const { area } = cachedStats;
|
|
91
|
-
|
|
92
|
-
const halfXLength = Math.abs(start.x - end.x) / 2;
|
|
93
|
-
const halfYLength = Math.abs(start.y - end.y) / 2;
|
|
94
|
-
|
|
95
|
-
const points = [];
|
|
96
|
-
|
|
97
|
-
const center = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
|
98
|
-
|
|
99
|
-
if (halfXLength > halfYLength) {
|
|
100
|
-
// X-axis major
|
|
101
|
-
// Major axis
|
|
102
|
-
points.push({ x: center.x - halfXLength, y: center.y });
|
|
103
|
-
points.push({ x: center.x + halfXLength, y: center.y });
|
|
104
|
-
// Minor axis
|
|
105
|
-
points.push({ x: center.x, y: center.y - halfYLength });
|
|
106
|
-
points.push({ x: center.x, y: center.y + halfYLength });
|
|
107
|
-
} else {
|
|
108
|
-
// Y-axis major
|
|
109
|
-
// Major axis
|
|
110
|
-
points.push({ x: center.x, y: center.y - halfYLength });
|
|
111
|
-
points.push({ x: center.x, y: center.y + halfYLength });
|
|
112
|
-
// Minor axis
|
|
113
|
-
points.push({ x: center.x - halfXLength, y: center.y });
|
|
114
|
-
points.push({ x: center.x + halfXLength, y: center.y });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const trackingIdentifierTextValue =
|
|
118
|
-
"cornerstoneTools@^4.0.0:EllipticalRoi";
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
area,
|
|
122
|
-
points,
|
|
123
|
-
trackingIdentifierTextValue,
|
|
124
|
-
finding,
|
|
125
|
-
findingSites: findingSites || []
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
EllipticalRoi.toolType = ELLIPTICALROI;
|
|
131
|
-
EllipticalRoi.utilityToolType = ELLIPTICALROI;
|
|
132
|
-
EllipticalRoi.TID300Representation = TID300Ellipse;
|
|
133
|
-
EllipticalRoi.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
|
|
134
|
-
if (!TrackingIdentifier.includes(":")) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":");
|
|
139
|
-
|
|
140
|
-
if (cornerstone4Tag !== CORNERSTONE_4_TAG) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return toolType === ELLIPTICALROI;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
MeasurementReport.registerTool(EllipticalRoi);
|
|
148
|
-
|
|
149
|
-
export default EllipticalRoi;
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { utilities } from "dcmjs";
|
|
2
|
-
|
|
3
|
-
import MeasurementReport from "./MeasurementReport";
|
|
4
|
-
import CORNERSTONE_4_TAG from "./cornerstone4Tag";
|
|
5
|
-
|
|
6
|
-
const { Polyline: TID300Polyline } = utilities.TID300;
|
|
7
|
-
|
|
8
|
-
class FreehandRoi {
|
|
9
|
-
static getMeasurementData(MeasurementGroup) {
|
|
10
|
-
const { defaultState, SCOORDGroup, NUMGroup } =
|
|
11
|
-
MeasurementReport.getSetupMeasurementData(MeasurementGroup);
|
|
12
|
-
|
|
13
|
-
const state = {
|
|
14
|
-
...defaultState,
|
|
15
|
-
toolType: FreehandRoi.toolType,
|
|
16
|
-
handles: {
|
|
17
|
-
points: [],
|
|
18
|
-
textBox: {
|
|
19
|
-
active: false,
|
|
20
|
-
hasMoved: false,
|
|
21
|
-
movesIndependently: false,
|
|
22
|
-
drawnIndependently: true,
|
|
23
|
-
allowedOutsideImage: true,
|
|
24
|
-
hasBoundingBox: true
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
cachedStats: {
|
|
28
|
-
area: NUMGroup ? NUMGroup.MeasuredValueSequence.NumericValue : 0
|
|
29
|
-
},
|
|
30
|
-
color: undefined,
|
|
31
|
-
invalidated: true
|
|
32
|
-
};
|
|
33
|
-
const { GraphicData } = SCOORDGroup;
|
|
34
|
-
for (let i = 0; i < GraphicData.length; i += 2) {
|
|
35
|
-
state.handles.points.push({
|
|
36
|
-
x: GraphicData[i],
|
|
37
|
-
y: GraphicData[i + 1]
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return state;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static getTID300RepresentationArguments(tool) {
|
|
45
|
-
const { handles, finding, findingSites, cachedStats = {} } = tool;
|
|
46
|
-
const { points } = handles;
|
|
47
|
-
const { area = 0, perimeter = 0 } = cachedStats;
|
|
48
|
-
|
|
49
|
-
const trackingIdentifierTextValue =
|
|
50
|
-
"cornerstoneTools@^4.0.0:FreehandRoi";
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
points,
|
|
54
|
-
area,
|
|
55
|
-
perimeter,
|
|
56
|
-
trackingIdentifierTextValue,
|
|
57
|
-
finding,
|
|
58
|
-
findingSites: findingSites || []
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
FreehandRoi.toolType = "FreehandRoi";
|
|
64
|
-
FreehandRoi.utilityToolType = "FreehandRoi";
|
|
65
|
-
FreehandRoi.TID300Representation = TID300Polyline;
|
|
66
|
-
FreehandRoi.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
|
|
67
|
-
if (!TrackingIdentifier.includes(":")) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":");
|
|
72
|
-
|
|
73
|
-
if (cornerstone4Tag !== CORNERSTONE_4_TAG) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return toolType === FreehandRoi.toolType;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
MeasurementReport.registerTool(FreehandRoi);
|
|
81
|
-
|
|
82
|
-
export default FreehandRoi;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { utilities } from "dcmjs";
|
|
2
|
-
import MeasurementReport from "./MeasurementReport";
|
|
3
|
-
import CORNERSTONE_4_TAG from "./cornerstone4Tag";
|
|
4
|
-
|
|
5
|
-
const { Length: TID300Length } = utilities.TID300;
|
|
6
|
-
|
|
7
|
-
const LENGTH = "Length";
|
|
8
|
-
|
|
9
|
-
class Length {
|
|
10
|
-
// TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport.
|
|
11
|
-
static getMeasurementData(MeasurementGroup) {
|
|
12
|
-
const { defaultState, NUMGroup, SCOORDGroup } =
|
|
13
|
-
MeasurementReport.getSetupMeasurementData(MeasurementGroup);
|
|
14
|
-
|
|
15
|
-
const state = {
|
|
16
|
-
...defaultState,
|
|
17
|
-
length: NUMGroup.MeasuredValueSequence.NumericValue,
|
|
18
|
-
toolType: Length.toolType,
|
|
19
|
-
handles: {
|
|
20
|
-
start: {},
|
|
21
|
-
end: {},
|
|
22
|
-
textBox: {
|
|
23
|
-
hasMoved: false,
|
|
24
|
-
movesIndependently: false,
|
|
25
|
-
drawnIndependently: true,
|
|
26
|
-
allowedOutsideImage: true,
|
|
27
|
-
hasBoundingBox: true
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
[
|
|
33
|
-
state.handles.start.x,
|
|
34
|
-
state.handles.start.y,
|
|
35
|
-
state.handles.end.x,
|
|
36
|
-
state.handles.end.y
|
|
37
|
-
] = SCOORDGroup.GraphicData;
|
|
38
|
-
|
|
39
|
-
return state;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static getTID300RepresentationArguments(tool) {
|
|
43
|
-
const { handles, finding, findingSites } = tool;
|
|
44
|
-
const point1 = handles.start;
|
|
45
|
-
const point2 = handles.end;
|
|
46
|
-
const distance = tool.length;
|
|
47
|
-
|
|
48
|
-
const trackingIdentifierTextValue = "cornerstoneTools@^4.0.0:Length";
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
point1,
|
|
52
|
-
point2,
|
|
53
|
-
distance,
|
|
54
|
-
trackingIdentifierTextValue,
|
|
55
|
-
finding,
|
|
56
|
-
findingSites: findingSites || []
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
Length.toolType = LENGTH;
|
|
62
|
-
Length.utilityToolType = LENGTH;
|
|
63
|
-
Length.TID300Representation = TID300Length;
|
|
64
|
-
Length.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
|
|
65
|
-
if (!TrackingIdentifier.includes(":")) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":");
|
|
70
|
-
|
|
71
|
-
if (cornerstone4Tag !== CORNERSTONE_4_TAG) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return toolType === LENGTH;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
MeasurementReport.registerTool(Length);
|
|
79
|
-
|
|
80
|
-
export default Length;
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import { normalizers, data, utilities, derivations } from "dcmjs";
|
|
2
|
-
|
|
3
|
-
import { toArray, codeMeaningEquals } from "../helpers";
|
|
4
|
-
|
|
5
|
-
const { TID1500, addAccessors } = utilities;
|
|
6
|
-
|
|
7
|
-
const { StructuredReport } = derivations;
|
|
8
|
-
|
|
9
|
-
const { Normalizer } = normalizers;
|
|
10
|
-
|
|
11
|
-
const { TID1500MeasurementReport, TID1501MeasurementGroup } = TID1500;
|
|
12
|
-
|
|
13
|
-
const { DicomMetaDictionary } = data;
|
|
14
|
-
|
|
15
|
-
const FINDING = { CodingSchemeDesignator: "DCM", CodeValue: "121071" };
|
|
16
|
-
const FINDING_SITE = { CodingSchemeDesignator: "SCT", CodeValue: "363698007" };
|
|
17
|
-
const FINDING_SITE_OLD = { CodingSchemeDesignator: "SRT", CodeValue: "G-C0E3" };
|
|
18
|
-
|
|
19
|
-
const codeValueMatch = (group, code, oldCode) => {
|
|
20
|
-
const { ConceptNameCodeSequence } = group;
|
|
21
|
-
if (!ConceptNameCodeSequence) return;
|
|
22
|
-
const { CodingSchemeDesignator, CodeValue } = ConceptNameCodeSequence;
|
|
23
|
-
return (
|
|
24
|
-
(CodingSchemeDesignator == code.CodingSchemeDesignator &&
|
|
25
|
-
CodeValue == code.CodeValue) ||
|
|
26
|
-
(oldCode &&
|
|
27
|
-
CodingSchemeDesignator == oldCode.CodingSchemeDesignator &&
|
|
28
|
-
CodeValue == oldCode.CodeValue)
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function getTID300ContentItem(
|
|
33
|
-
tool,
|
|
34
|
-
toolType,
|
|
35
|
-
ReferencedSOPSequence,
|
|
36
|
-
toolClass
|
|
37
|
-
) {
|
|
38
|
-
const args = toolClass.getTID300RepresentationArguments(tool);
|
|
39
|
-
args.ReferencedSOPSequence = ReferencedSOPSequence;
|
|
40
|
-
|
|
41
|
-
const TID300Measurement = new toolClass.TID300Representation(args);
|
|
42
|
-
|
|
43
|
-
return TID300Measurement;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getMeasurementGroup(toolType, toolData, ReferencedSOPSequence) {
|
|
47
|
-
const toolTypeData = toolData[toolType];
|
|
48
|
-
const toolClass =
|
|
49
|
-
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_TOOL_TYPE[toolType];
|
|
50
|
-
if (
|
|
51
|
-
!toolTypeData ||
|
|
52
|
-
!toolTypeData.data ||
|
|
53
|
-
!toolTypeData.data.length ||
|
|
54
|
-
!toolClass
|
|
55
|
-
) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Loop through the array of tool instances
|
|
60
|
-
// for this tool
|
|
61
|
-
const Measurements = toolTypeData.data.map(tool => {
|
|
62
|
-
return getTID300ContentItem(
|
|
63
|
-
tool,
|
|
64
|
-
toolType,
|
|
65
|
-
ReferencedSOPSequence,
|
|
66
|
-
toolClass
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return new TID1501MeasurementGroup(Measurements);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export default class MeasurementReport {
|
|
74
|
-
static getSetupMeasurementData(MeasurementGroup) {
|
|
75
|
-
const { ContentSequence } = MeasurementGroup;
|
|
76
|
-
|
|
77
|
-
const contentSequenceArr = toArray(ContentSequence);
|
|
78
|
-
const findingGroup = contentSequenceArr.find(group =>
|
|
79
|
-
codeValueMatch(group, FINDING)
|
|
80
|
-
);
|
|
81
|
-
const findingSiteGroups =
|
|
82
|
-
contentSequenceArr.filter(group =>
|
|
83
|
-
codeValueMatch(group, FINDING_SITE, FINDING_SITE_OLD)
|
|
84
|
-
) || [];
|
|
85
|
-
const NUMGroup = contentSequenceArr.find(
|
|
86
|
-
group => group.ValueType === "NUM"
|
|
87
|
-
);
|
|
88
|
-
const SCOORDGroup = toArray(NUMGroup.ContentSequence).find(
|
|
89
|
-
group => group.ValueType === "SCOORD"
|
|
90
|
-
);
|
|
91
|
-
const { ReferencedSOPSequence } = SCOORDGroup.ContentSequence;
|
|
92
|
-
const { ReferencedSOPInstanceUID, ReferencedFrameNumber } =
|
|
93
|
-
ReferencedSOPSequence;
|
|
94
|
-
|
|
95
|
-
const defaultState = {
|
|
96
|
-
sopInstanceUid: ReferencedSOPInstanceUID,
|
|
97
|
-
frameIndex: ReferencedFrameNumber || 1,
|
|
98
|
-
complete: true,
|
|
99
|
-
finding: findingGroup
|
|
100
|
-
? addAccessors(findingGroup.ConceptCodeSequence)
|
|
101
|
-
: undefined,
|
|
102
|
-
findingSites: findingSiteGroups.map(fsg => {
|
|
103
|
-
return addAccessors(fsg.ConceptCodeSequence);
|
|
104
|
-
})
|
|
105
|
-
};
|
|
106
|
-
if (defaultState.finding) {
|
|
107
|
-
defaultState.description = defaultState.finding.CodeMeaning;
|
|
108
|
-
}
|
|
109
|
-
const findingSite =
|
|
110
|
-
defaultState.findingSites && defaultState.findingSites[0];
|
|
111
|
-
if (findingSite) {
|
|
112
|
-
defaultState.location =
|
|
113
|
-
(findingSite[0] && findingSite[0].CodeMeaning) ||
|
|
114
|
-
findingSite.CodeMeaning;
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
defaultState,
|
|
118
|
-
findingGroup,
|
|
119
|
-
findingSiteGroups,
|
|
120
|
-
NUMGroup,
|
|
121
|
-
SCOORDGroup,
|
|
122
|
-
ReferencedSOPSequence,
|
|
123
|
-
ReferencedSOPInstanceUID,
|
|
124
|
-
ReferencedFrameNumber
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
static generateReport(toolState, metadataProvider, options) {
|
|
129
|
-
// ToolState for array of imageIDs to a Report
|
|
130
|
-
// Assume Cornerstone metadata provider has access to Study / Series / Sop Instance UID
|
|
131
|
-
|
|
132
|
-
let allMeasurementGroups = [];
|
|
133
|
-
const firstImageId = Object.keys(toolState)[0];
|
|
134
|
-
if (!firstImageId) {
|
|
135
|
-
throw new Error("No measurements provided.");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/* Patient ID
|
|
139
|
-
Warning - Missing attribute or value that would be needed to build DICOMDIR - Patient ID
|
|
140
|
-
Warning - Missing attribute or value that would be needed to build DICOMDIR - Study Date
|
|
141
|
-
Warning - Missing attribute or value that would be needed to build DICOMDIR - Study Time
|
|
142
|
-
Warning - Missing attribute or value that would be needed to build DICOMDIR - Study ID
|
|
143
|
-
*/
|
|
144
|
-
const generalSeriesModule = metadataProvider.get(
|
|
145
|
-
"generalSeriesModule",
|
|
146
|
-
firstImageId
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
//const sopCommonModule = metadataProvider.get('sopCommonModule', firstImageId);
|
|
150
|
-
|
|
151
|
-
// NOTE: We are getting the Series and Study UIDs from the first imageId of the toolState
|
|
152
|
-
// which means that if the toolState is for multiple series, the report will have the incorrect
|
|
153
|
-
// SeriesInstanceUIDs
|
|
154
|
-
const { studyInstanceUID, seriesInstanceUID } = generalSeriesModule;
|
|
155
|
-
|
|
156
|
-
// Loop through each image in the toolData
|
|
157
|
-
Object.keys(toolState).forEach(imageId => {
|
|
158
|
-
const sopCommonModule = metadataProvider.get(
|
|
159
|
-
"sopCommonModule",
|
|
160
|
-
imageId
|
|
161
|
-
);
|
|
162
|
-
const frameNumber = metadataProvider.get("frameNumber", imageId);
|
|
163
|
-
const toolData = toolState[imageId];
|
|
164
|
-
const toolTypes = Object.keys(toolData);
|
|
165
|
-
|
|
166
|
-
const ReferencedSOPSequence = {
|
|
167
|
-
ReferencedSOPClassUID: sopCommonModule.sopClassUID,
|
|
168
|
-
ReferencedSOPInstanceUID: sopCommonModule.sopInstanceUID
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
Normalizer.isMultiframeSOPClassUID(sopCommonModule.sopClassUID)
|
|
173
|
-
) {
|
|
174
|
-
ReferencedSOPSequence.ReferencedFrameNumber = frameNumber;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Loop through each tool type for the image
|
|
178
|
-
const measurementGroups = [];
|
|
179
|
-
|
|
180
|
-
toolTypes.forEach(toolType => {
|
|
181
|
-
const group = getMeasurementGroup(
|
|
182
|
-
toolType,
|
|
183
|
-
toolData,
|
|
184
|
-
ReferencedSOPSequence
|
|
185
|
-
);
|
|
186
|
-
if (group) {
|
|
187
|
-
measurementGroups.push(group);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
allMeasurementGroups =
|
|
192
|
-
allMeasurementGroups.concat(measurementGroups);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const MeasurementReport = new TID1500MeasurementReport(
|
|
196
|
-
{ TID1501MeasurementGroups: allMeasurementGroups },
|
|
197
|
-
options
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
// TODO: what is the correct metaheader
|
|
201
|
-
// http://dicom.nema.org/medical/Dicom/current/output/chtml/part10/chapter_7.html
|
|
202
|
-
// TODO: move meta creation to happen in derivations.js
|
|
203
|
-
const fileMetaInformationVersionArray = new Uint8Array(2);
|
|
204
|
-
fileMetaInformationVersionArray[1] = 1;
|
|
205
|
-
|
|
206
|
-
const derivationSourceDataset = {
|
|
207
|
-
StudyInstanceUID: studyInstanceUID,
|
|
208
|
-
SeriesInstanceUID: seriesInstanceUID
|
|
209
|
-
//SOPInstanceUID: sopInstanceUID, // TODO: Necessary?
|
|
210
|
-
//SOPClassUID: sopClassUID,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const _meta = {
|
|
214
|
-
FileMetaInformationVersion: {
|
|
215
|
-
Value: [fileMetaInformationVersionArray.buffer],
|
|
216
|
-
vr: "OB"
|
|
217
|
-
},
|
|
218
|
-
//MediaStorageSOPClassUID
|
|
219
|
-
//MediaStorageSOPInstanceUID: sopCommonModule.sopInstanceUID,
|
|
220
|
-
TransferSyntaxUID: {
|
|
221
|
-
Value: ["1.2.840.10008.1.2.1"],
|
|
222
|
-
vr: "UI"
|
|
223
|
-
},
|
|
224
|
-
ImplementationClassUID: {
|
|
225
|
-
Value: [DicomMetaDictionary.uid()], // TODO: could be git hash or other valid id
|
|
226
|
-
vr: "UI"
|
|
227
|
-
},
|
|
228
|
-
ImplementationVersionName: {
|
|
229
|
-
Value: ["dcmjs"],
|
|
230
|
-
vr: "SH"
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const _vrMap = {
|
|
235
|
-
PixelData: "OW"
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
derivationSourceDataset._meta = _meta;
|
|
239
|
-
derivationSourceDataset._vrMap = _vrMap;
|
|
240
|
-
|
|
241
|
-
const report = new StructuredReport([derivationSourceDataset]);
|
|
242
|
-
|
|
243
|
-
const contentItem = MeasurementReport.contentItem(
|
|
244
|
-
derivationSourceDataset
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
// Merge the derived dataset with the content from the Measurement Report
|
|
248
|
-
report.dataset = Object.assign(report.dataset, contentItem);
|
|
249
|
-
report.dataset._meta = _meta;
|
|
250
|
-
|
|
251
|
-
return report;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Generate Cornerstone tool state from dataset
|
|
256
|
-
* @param {object} dataset dataset
|
|
257
|
-
* @param {object} hooks
|
|
258
|
-
* @param {function} hooks.getToolClass Function to map dataset to a tool class
|
|
259
|
-
* @returns
|
|
260
|
-
*/
|
|
261
|
-
static generateToolState(dataset, hooks = {}) {
|
|
262
|
-
// For now, bail out if the dataset is not a TID1500 SR with length measurements
|
|
263
|
-
if (dataset.ContentTemplateSequence.TemplateIdentifier !== "1500") {
|
|
264
|
-
throw new Error(
|
|
265
|
-
"This package can currently only interpret DICOM SR TID 1500"
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const REPORT = "Imaging Measurements";
|
|
270
|
-
const GROUP = "Measurement Group";
|
|
271
|
-
const TRACKING_IDENTIFIER = "Tracking Identifier";
|
|
272
|
-
|
|
273
|
-
// Identify the Imaging Measurements
|
|
274
|
-
const imagingMeasurementContent = toArray(dataset.ContentSequence).find(
|
|
275
|
-
codeMeaningEquals(REPORT)
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// Retrieve the Measurements themselves
|
|
279
|
-
const measurementGroups = toArray(
|
|
280
|
-
imagingMeasurementContent.ContentSequence
|
|
281
|
-
).filter(codeMeaningEquals(GROUP));
|
|
282
|
-
|
|
283
|
-
// For each of the supported measurement types, compute the measurement data
|
|
284
|
-
const measurementData = {};
|
|
285
|
-
|
|
286
|
-
const cornerstoneToolClasses =
|
|
287
|
-
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
|
288
|
-
|
|
289
|
-
const registeredToolClasses = [];
|
|
290
|
-
|
|
291
|
-
Object.keys(cornerstoneToolClasses).forEach(key => {
|
|
292
|
-
registeredToolClasses.push(cornerstoneToolClasses[key]);
|
|
293
|
-
measurementData[key] = [];
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
measurementGroups.forEach(measurementGroup => {
|
|
297
|
-
const measurementGroupContentSequence = toArray(
|
|
298
|
-
measurementGroup.ContentSequence
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
const TrackingIdentifierGroup =
|
|
302
|
-
measurementGroupContentSequence.find(
|
|
303
|
-
contentItem =>
|
|
304
|
-
contentItem.ConceptNameCodeSequence.CodeMeaning ===
|
|
305
|
-
TRACKING_IDENTIFIER
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
const TrackingIdentifierValue = TrackingIdentifierGroup.TextValue;
|
|
309
|
-
|
|
310
|
-
const toolClass = hooks.getToolClass
|
|
311
|
-
? hooks.getToolClass(
|
|
312
|
-
measurementGroup,
|
|
313
|
-
dataset,
|
|
314
|
-
registeredToolClasses
|
|
315
|
-
)
|
|
316
|
-
: registeredToolClasses.find(tc =>
|
|
317
|
-
tc.isValidCornerstoneTrackingIdentifier(
|
|
318
|
-
TrackingIdentifierValue
|
|
319
|
-
)
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
if (toolClass) {
|
|
323
|
-
const measurement =
|
|
324
|
-
toolClass.getMeasurementData(measurementGroup);
|
|
325
|
-
|
|
326
|
-
console.log(`=== ${toolClass.toolType} ===`);
|
|
327
|
-
console.log(measurement);
|
|
328
|
-
|
|
329
|
-
measurementData[toolClass.toolType].push(measurement);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// NOTE: There is no way of knowing the cornerstone imageIds as that could be anything.
|
|
334
|
-
// That is up to the consumer to derive from the SOPInstanceUIDs.
|
|
335
|
-
return measurementData;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
static registerTool(toolClass) {
|
|
339
|
-
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE[
|
|
340
|
-
toolClass.utilityToolType
|
|
341
|
-
] = toolClass;
|
|
342
|
-
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_TOOL_TYPE[
|
|
343
|
-
toolClass.toolType
|
|
344
|
-
] = toolClass;
|
|
345
|
-
MeasurementReport.MEASUREMENT_BY_TOOLTYPE[toolClass.toolType] =
|
|
346
|
-
toolClass.utilityToolType;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
MeasurementReport.MEASUREMENT_BY_TOOLTYPE = {};
|
|
351
|
-
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE = {};
|
|
352
|
-
MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_TOOL_TYPE = {};
|