Skip to content

Commit c65b930

Browse files
authored
feat(cornerstone): Feature add cornerstone adapters (#225)
* feature(dcmjs): Adding cobb-angle support * feat(dcmjs): Add adapter for CobbAngle, and add description and location mapping * fix(dcmjs): Fix the arrow, cobb and length to all get the description/group in the same way * feat(dcmjs):Add adapters for FreehandRoi, RectangleRoi and Angle * fix(dcmjs): Fixed the angle and rectangle cornerstone adapters * fix(dcmjs): Store an optional second point for the arrow annotation The point TID300 normally takes 1 point, but seems to be ok if an extra one is added for the source of the indicator. * fix(dcmjs):Use the SCT codes rather than SRT as requested
1 parent 70b2433 commit c65b930

File tree

15 files changed

+565
-178
lines changed

15 files changed

+565
-178
lines changed

src/adapters/Cornerstone/Angle.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import MeasurementReport from "./MeasurementReport.js";
2+
import TID300CobbAngle from "../../utilities/TID300/CobbAngle.js";
3+
import CORNERSTONE_4_TAG from "./cornerstone4Tag";
4+
5+
const ANGLE = "Angle";
6+
7+
class Angle {
8+
constructor() {}
9+
10+
/**
11+
* Generate TID300 measurement data for a plane angle measurement - use a CobbAngle, but label it as Angle
12+
* @param MeasurementGroup
13+
* @returns
14+
*/
15+
static getMeasurementData(MeasurementGroup) {
16+
const {
17+
defaultState,
18+
NUMGroup,
19+
SCOORDGroup
20+
} = MeasurementReport.getSetupMeasurementData(MeasurementGroup);
21+
22+
const state = {
23+
...defaultState,
24+
rAngle: NUMGroup.MeasuredValueSequence.NumericValue,
25+
toolType: Angle.toolType,
26+
handles: {
27+
start: {},
28+
middle: {},
29+
end: {},
30+
textBox: {
31+
hasMoved: false,
32+
movesIndependently: false,
33+
drawnIndependently: true,
34+
allowedOutsideImage: true,
35+
hasBoundingBox: true
36+
}
37+
}
38+
};
39+
40+
[
41+
state.handles.start.x,
42+
state.handles.start.y,
43+
state.handles.middle.x,
44+
state.handles.middle.y,
45+
state.handles.middle.x,
46+
state.handles.middle.y,
47+
state.handles.end.x,
48+
state.handles.end.y
49+
] = SCOORDGroup.GraphicData;
50+
51+
return state;
52+
}
53+
54+
static getTID300RepresentationArguments(tool) {
55+
const { handles, finding, findingSites } = tool;
56+
const point1 = handles.start;
57+
const point2 = handles.middle;
58+
const point3 = handles.middle;
59+
const point4 = handles.end;
60+
const rAngle = tool.rAngle;
61+
62+
const trackingIdentifierTextValue = "cornerstoneTools@^4.0.0:Angle";
63+
64+
return {
65+
point1,
66+
point2,
67+
point3,
68+
point4,
69+
rAngle,
70+
trackingIdentifierTextValue,
71+
finding,
72+
findingSites: findingSites || []
73+
};
74+
}
75+
}
76+
77+
Angle.toolType = ANGLE;
78+
Angle.utilityToolType = ANGLE;
79+
Angle.TID300Representation = TID300CobbAngle;
80+
Angle.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
81+
if (!TrackingIdentifier.includes(":")) {
82+
return false;
83+
}
84+
85+
const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":");
86+
87+
if (cornerstone4Tag !== CORNERSTONE_4_TAG) {
88+
return false;
89+
}
90+
91+
return toolType === ANGLE;
92+
};
93+
94+
MeasurementReport.registerTool(Angle);
95+
96+
export default Angle;

src/adapters/Cornerstone/ArrowAnnotate.js

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,26 @@
11
import MeasurementReport from "./MeasurementReport.js";
22
import TID300Point from "../../utilities/TID300/Point.js";
33
import CORNERSTONE_4_TAG from "./cornerstone4Tag";
4-
import { toArray } from "../helpers.js";
54

65
const ARROW_ANNOTATE = "ArrowAnnotate";
7-
const FINDING = "121071";
8-
const FINDING_SITE = "G-C0E3";
96
const CORNERSTONEFREETEXT = "CORNERSTONEFREETEXT";
107

118
class ArrowAnnotate {
129
constructor() {}
1310

14-
// TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport.
1511
static getMeasurementData(MeasurementGroup) {
16-
const { ContentSequence } = MeasurementGroup;
17-
18-
const NUMGroup = toArray(ContentSequence).find(
19-
group => group.ValueType === "NUM"
20-
);
21-
22-
const SCOORDGroup = toArray(NUMGroup.ContentSequence).find(
23-
group => group.ValueType === "SCOORD"
24-
);
25-
26-
const findingGroup = toArray(ContentSequence).find(
27-
group => group.ConceptNameCodeSequence.CodeValue === FINDING
28-
);
29-
30-
const findingSiteGroups = toArray(ContentSequence).filter(
31-
group => group.ConceptNameCodeSequence.CodeValue === FINDING_SITE
32-
);
12+
const {
13+
defaultState,
14+
SCOORDGroup,
15+
findingGroup
16+
} = MeasurementReport.getSetupMeasurementData(MeasurementGroup);
3317

3418
const text = findingGroup.ConceptCodeSequence.CodeMeaning;
3519

3620
const { GraphicData } = SCOORDGroup;
3721

38-
const { ReferencedSOPSequence } = SCOORDGroup.ContentSequence;
39-
const {
40-
ReferencedSOPInstanceUID,
41-
ReferencedFrameNumber
42-
} = ReferencedSOPSequence;
4322
const state = {
44-
sopInstanceUid: ReferencedSOPInstanceUID,
45-
frameIndex: ReferencedFrameNumber || 0,
23+
...defaultState,
4624
toolType: ArrowAnnotate.toolType,
4725
active: false,
4826
handles: {
@@ -52,11 +30,17 @@ class ArrowAnnotate {
5230
highlight: true,
5331
active: false
5432
},
55-
// TODO: How do we choose where the end goes?
56-
// Just put it pointing from the bottom right for now?
33+
// Use a generic offset if the stored data doesn't have the endpoint, otherwise
34+
// use the actual endpoint.
5735
end: {
58-
x: GraphicData[0] + 20,
59-
y: GraphicData[1] + 20,
36+
x:
37+
GraphicData.length == 4
38+
? GraphicData[2]
39+
: GraphicData[0] + 20,
40+
y:
41+
GraphicData.length == 4
42+
? GraphicData[3]
43+
: GraphicData[1] + 20,
6044
highlight: true,
6145
active: false
6246
},
@@ -70,20 +54,14 @@ class ArrowAnnotate {
7054
},
7155
invalidated: true,
7256
text,
73-
visible: true,
74-
finding: findingGroup
75-
? findingGroup.ConceptCodeSequence
76-
: undefined,
77-
findingSites: findingSiteGroups.map(fsg => {
78-
return { ...fsg.ConceptCodeSequence };
79-
})
57+
visible: true
8058
};
8159

8260
return state;
8361
}
8462

8563
static getTID300RepresentationArguments(tool) {
86-
const points = [tool.handles.start];
64+
const points = [tool.handles.start, tool.handles.end];
8765

8866
let { finding, findingSites } = tool;
8967

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import MeasurementReport from "./MeasurementReport.js";
2+
import TID300CobbAngle from "../../utilities/TID300/CobbAngle.js";
3+
import CORNERSTONE_4_TAG from "./cornerstone4Tag";
4+
5+
const COBB_ANGLE = "CobbAngle";
6+
7+
class CobbAngle {
8+
constructor() {}
9+
10+
// TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport.
11+
static getMeasurementData(MeasurementGroup) {
12+
const {
13+
defaultState,
14+
NUMGroup,
15+
SCOORDGroup
16+
} = MeasurementReport.getSetupMeasurementData(MeasurementGroup);
17+
18+
const state = {
19+
...defaultState,
20+
rAngle: NUMGroup.MeasuredValueSequence.NumericValue,
21+
toolType: CobbAngle.toolType,
22+
handles: {
23+
start: {},
24+
end: {},
25+
start2: {
26+
highlight: true,
27+
drawnIndependently: true
28+
},
29+
end2: {
30+
highlight: true,
31+
drawnIndependently: true
32+
},
33+
textBox: {
34+
hasMoved: false,
35+
movesIndependently: false,
36+
drawnIndependently: true,
37+
allowedOutsideImage: true,
38+
hasBoundingBox: true
39+
}
40+
}
41+
};
42+
43+
[
44+
state.handles.start.x,
45+
state.handles.start.y,
46+
state.handles.end.x,
47+
state.handles.end.y,
48+
state.handles.start2.x,
49+
state.handles.start2.y,
50+
state.handles.end2.x,
51+
state.handles.end2.y
52+
] = SCOORDGroup.GraphicData;
53+
54+
return state;
55+
}
56+
57+
static getTID300RepresentationArguments(tool) {
58+
const { handles, finding, findingSites } = tool;
59+
const point1 = handles.start;
60+
const point2 = handles.end;
61+
const point3 = handles.start2;
62+
const point4 = handles.end2;
63+
const rAngle = tool.rAngle;
64+
65+
const trackingIdentifierTextValue = "cornerstoneTools@^4.0.0:CobbAngle";
66+
67+
return {
68+
point1,
69+
point2,
70+
point3,
71+
point4,
72+
rAngle,
73+
trackingIdentifierTextValue,
74+
finding,
75+
findingSites: findingSites || []
76+
};
77+
}
78+
}
79+
80+
CobbAngle.toolType = COBB_ANGLE;
81+
CobbAngle.utilityToolType = COBB_ANGLE;
82+
CobbAngle.TID300Representation = TID300CobbAngle;
83+
CobbAngle.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => {
84+
if (!TrackingIdentifier.includes(":")) {
85+
return false;
86+
}
87+
88+
const [cornerstone4Tag, toolType] = TrackingIdentifier.split(":");
89+
90+
if (cornerstone4Tag !== CORNERSTONE_4_TAG) {
91+
return false;
92+
}
93+
94+
return toolType === COBB_ANGLE;
95+
};
96+
97+
MeasurementReport.registerTool(CobbAngle);
98+
99+
export default CobbAngle;

src/adapters/Cornerstone/EllipticalRoi.js

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,11 @@ class EllipticalRoi {
1212

1313
// TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport.
1414
static getMeasurementData(MeasurementGroup) {
15-
const { ContentSequence } = MeasurementGroup;
16-
17-
const findingGroup = toArray(ContentSequence).find(
18-
group => group.ConceptNameCodeSequence.CodeValue === FINDING
19-
);
20-
21-
const findingSiteGroups = toArray(ContentSequence).filter(
22-
group => group.ConceptNameCodeSequence.CodeValue === FINDING_SITE
23-
);
24-
25-
const NUMGroup = toArray(ContentSequence).find(
26-
group => group.ValueType === "NUM"
27-
);
28-
29-
const SCOORDGroup = toArray(NUMGroup.ContentSequence).find(
30-
group => group.ValueType === "SCOORD"
31-
);
15+
const {
16+
defaultState,
17+
NUMGroup,
18+
SCOORDGroup
19+
} = MeasurementReport.getSetupMeasurementData(MeasurementGroup);
3220

3321
const { GraphicData } = SCOORDGroup;
3422

@@ -66,14 +54,8 @@ class EllipticalRoi {
6654
x: majorAxis[1].x - minorAxisDirection.x * halfMinorAxisLength,
6755
y: majorAxis[1].y - minorAxisDirection.y * halfMinorAxisLength
6856
};
69-
const { ReferencedSOPSequence } = SCOORDGroup.ContentSequence;
70-
const {
71-
ReferencedSOPInstanceUID,
72-
ReferencedFrameNumber
73-
} = ReferencedSOPSequence;
7457
const state = {
75-
sopInstanceUid: ReferencedSOPInstanceUID,
76-
frameIndex: ReferencedFrameNumber || 0,
58+
...defaultState,
7759
toolType: EllipticalRoi.toolType,
7860
active: false,
7961
cachedStats: {
@@ -102,13 +84,7 @@ class EllipticalRoi {
10284
}
10385
},
10486
invalidated: true,
105-
visible: true,
106-
finding: findingGroup
107-
? findingGroup.ConceptCodeSequence
108-
: undefined,
109-
findingSites: findingSiteGroups.map(fsg => {
110-
return { ...fsg.ConceptCodeSequence };
111-
})
87+
visible: true
11288
};
11389

11490
return state;

0 commit comments

Comments
 (0)