@cornerstonejs/tools 1.78.3 → 1.80.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/cjs/cursors/SVGCursorDescriptor.js +7 -0
- package/dist/cjs/cursors/SVGCursorDescriptor.js.map +1 -1
- package/dist/cjs/drawingSvg/drawHeight.d.ts +3 -0
- package/dist/cjs/drawingSvg/drawHeight.js +49 -0
- package/dist/cjs/drawingSvg/drawHeight.js.map +1 -0
- package/dist/cjs/drawingSvg/index.d.ts +2 -1
- package/dist/cjs/drawingSvg/index.js +3 -1
- package/dist/cjs/drawingSvg/index.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/tools/annotation/HeightTool.d.ts +40 -0
- package/dist/cjs/tools/annotation/HeightTool.js +463 -0
- package/dist/cjs/tools/annotation/HeightTool.js.map +1 -0
- package/dist/cjs/tools/index.d.ts +2 -1
- package/dist/cjs/tools/index.js +4 -2
- package/dist/cjs/tools/index.js.map +1 -1
- package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.d.ts +15 -8
- package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.js +189 -62
- package/dist/cjs/tools/segmentation/CircleROIStartEndThresholdTool.js.map +1 -1
- package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +17 -7
- package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.js +167 -52
- package/dist/cjs/tools/segmentation/RectangleROIStartEndThresholdTool.js.map +1 -1
- package/dist/cjs/types/CINETypes.d.ts +1 -0
- package/dist/cjs/types/ToolSpecificAnnotationTypes.d.ts +26 -4
- package/dist/cjs/utilities/cine/playClip.js +52 -5
- package/dist/cjs/utilities/cine/playClip.js.map +1 -1
- package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.d.ts +3 -0
- package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.js +31 -0
- package/dist/cjs/utilities/planar/filterAnnotationsWithinPlane.js.map +1 -0
- package/dist/cjs/utilities/planar/index.d.ts +3 -1
- package/dist/cjs/utilities/planar/index.js +4 -1
- package/dist/cjs/utilities/planar/index.js.map +1 -1
- package/dist/cjs/utilities/stackPrefetch/stackContextPrefetch.js +13 -2
- package/dist/cjs/utilities/stackPrefetch/stackContextPrefetch.js.map +1 -1
- package/dist/cjs/utilities/stackPrefetch/stackPrefetchUtils.js +0 -1
- package/dist/cjs/utilities/stackPrefetch/stackPrefetchUtils.js.map +1 -1
- package/dist/cjs/utilities/viewport/isViewportPreScaled.js +1 -4
- package/dist/cjs/utilities/viewport/isViewportPreScaled.js.map +1 -1
- package/dist/cjs/utilities/viewport/jumpToSlice.js +4 -6
- package/dist/cjs/utilities/viewport/jumpToSlice.js.map +1 -1
- package/dist/esm/cursors/SVGCursorDescriptor.js +7 -0
- package/dist/esm/cursors/SVGCursorDescriptor.js.map +1 -1
- package/dist/esm/drawingSvg/drawHeight.js +43 -0
- package/dist/esm/drawingSvg/drawHeight.js.map +1 -0
- package/dist/esm/drawingSvg/index.js +2 -1
- package/dist/esm/drawingSvg/index.js.map +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/tools/annotation/HeightTool.js +439 -0
- package/dist/esm/tools/annotation/HeightTool.js.map +1 -0
- package/dist/esm/tools/index.js +2 -1
- package/dist/esm/tools/index.js.map +1 -1
- package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +192 -66
- package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js.map +1 -1
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +168 -54
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js.map +1 -1
- package/dist/esm/utilities/cine/playClip.js +52 -5
- package/dist/esm/utilities/cine/playClip.js.map +1 -1
- package/dist/esm/utilities/planar/filterAnnotationsWithinPlane.js +27 -0
- package/dist/esm/utilities/planar/filterAnnotationsWithinPlane.js.map +1 -0
- package/dist/esm/utilities/planar/index.js +3 -1
- package/dist/esm/utilities/planar/index.js.map +1 -1
- package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.js +11 -2
- package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.js.map +1 -1
- package/dist/esm/utilities/stackPrefetch/stackPrefetchUtils.js +1 -2
- package/dist/esm/utilities/stackPrefetch/stackPrefetchUtils.js.map +1 -1
- package/dist/esm/utilities/viewport/isViewportPreScaled.js +2 -5
- package/dist/esm/utilities/viewport/isViewportPreScaled.js.map +1 -1
- package/dist/esm/utilities/viewport/jumpToSlice.js +5 -7
- package/dist/esm/utilities/viewport/jumpToSlice.js.map +1 -1
- package/dist/types/cursors/SVGCursorDescriptor.d.ts.map +1 -1
- package/dist/types/drawingSvg/drawHeight.d.ts +4 -0
- package/dist/types/drawingSvg/drawHeight.d.ts.map +1 -0
- package/dist/types/drawingSvg/index.d.ts +2 -1
- package/dist/types/drawingSvg/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/tools/annotation/HeightTool.d.ts +41 -0
- package/dist/types/tools/annotation/HeightTool.d.ts.map +1 -0
- package/dist/types/tools/index.d.ts +2 -1
- package/dist/types/tools/index.d.ts.map +1 -1
- package/dist/types/tools/segmentation/CircleROIStartEndThresholdTool.d.ts +15 -8
- package/dist/types/tools/segmentation/CircleROIStartEndThresholdTool.d.ts.map +1 -1
- package/dist/types/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts +17 -7
- package/dist/types/tools/segmentation/RectangleROIStartEndThresholdTool.d.ts.map +1 -1
- package/dist/types/types/CINETypes.d.ts +1 -0
- package/dist/types/types/CINETypes.d.ts.map +1 -1
- package/dist/types/types/ToolSpecificAnnotationTypes.d.ts +26 -4
- package/dist/types/types/ToolSpecificAnnotationTypes.d.ts.map +1 -1
- package/dist/types/utilities/cine/playClip.d.ts.map +1 -1
- package/dist/types/utilities/planar/filterAnnotationsWithinPlane.d.ts +4 -0
- package/dist/types/utilities/planar/filterAnnotationsWithinPlane.d.ts.map +1 -0
- package/dist/types/utilities/planar/index.d.ts +3 -1
- package/dist/types/utilities/planar/index.d.ts.map +1 -1
- package/dist/types/utilities/stackPrefetch/stackContextPrefetch.d.ts.map +1 -1
- package/dist/types/utilities/stackPrefetch/stackPrefetchUtils.d.ts.map +1 -1
- package/dist/types/utilities/viewport/isViewportPreScaled.d.ts.map +1 -1
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -3
- package/src/cursors/SVGCursorDescriptor.ts +7 -0
- package/src/drawingSvg/drawHeight.ts +90 -0
- package/src/drawingSvg/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/tools/annotation/HeightTool.ts +882 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/segmentation/CircleROIStartEndThresholdTool.ts +310 -102
- package/src/tools/segmentation/RectangleROIStartEndThresholdTool.ts +287 -77
- package/src/types/CINETypes.ts +3 -0
- package/src/types/ToolSpecificAnnotationTypes.ts +26 -4
- package/src/utilities/cine/playClip.ts +67 -8
- package/src/utilities/planar/filterAnnotationsWithinPlane.ts +76 -0
- package/src/utilities/planar/index.ts +3 -0
- package/src/utilities/stackPrefetch/stackContextPrefetch.ts +12 -2
- package/src/utilities/stackPrefetch/stackPrefetchUtils.ts +7 -5
- package/src/utilities/viewport/isViewportPreScaled.ts +2 -5
- package/src/utilities/viewport/jumpToSlice.ts +4 -4
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
import { Events } from '../../enums';
|
|
2
|
+
import { getEnabledElement, utilities as csUtils } from '@cornerstonejs/core';
|
|
3
|
+
import type { Types } from '@cornerstonejs/core';
|
|
4
|
+
|
|
5
|
+
import { getCalibratedLengthUnitsAndScale } from '../../utilities/getCalibratedUnits';
|
|
6
|
+
import { roundNumber } from '../../utilities';
|
|
7
|
+
import { AnnotationTool } from '../base';
|
|
8
|
+
import throttle from '../../utilities/throttle';
|
|
9
|
+
import {
|
|
10
|
+
addAnnotation,
|
|
11
|
+
getAnnotations,
|
|
12
|
+
removeAnnotation,
|
|
13
|
+
} from '../../stateManagement/annotation/annotationState';
|
|
14
|
+
import { isAnnotationLocked } from '../../stateManagement/annotation/annotationLocking';
|
|
15
|
+
import { isAnnotationVisible } from '../../stateManagement/annotation/annotationVisibility';
|
|
16
|
+
import {
|
|
17
|
+
triggerAnnotationCompleted,
|
|
18
|
+
triggerAnnotationModified,
|
|
19
|
+
} from '../../stateManagement/annotation/helpers/state';
|
|
20
|
+
import * as lineSegment from '../../utilities/math/line';
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
drawHandles as drawHandlesSvg,
|
|
24
|
+
drawHeight as drawHeightSvg,
|
|
25
|
+
drawLinkedTextBox as drawLinkedTextBoxSvg,
|
|
26
|
+
} from '../../drawingSvg';
|
|
27
|
+
import { state } from '../../store';
|
|
28
|
+
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
29
|
+
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
30
|
+
import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
resetElementCursor,
|
|
34
|
+
hideElementCursor,
|
|
35
|
+
} from '../../cursors/elementCursor';
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
EventTypes,
|
|
39
|
+
ToolHandle,
|
|
40
|
+
TextBoxHandle,
|
|
41
|
+
PublicToolProps,
|
|
42
|
+
ToolProps,
|
|
43
|
+
SVGDrawingHelper,
|
|
44
|
+
} from '../../types';
|
|
45
|
+
import { LengthAnnotation } from '../../types/ToolSpecificAnnotationTypes';
|
|
46
|
+
import { StyleSpecifier } from '../../types/AnnotationStyle';
|
|
47
|
+
|
|
48
|
+
const { transformWorldToIndex } = csUtils;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* HeightTool let you draw annotations that measures the height of two drawing
|
|
52
|
+
* points on a slice. You can use the HeightTool in all imaging planes even in oblique
|
|
53
|
+
* reconstructed planes. Note: annotation tools in cornerstone3DTools exists in the exact location
|
|
54
|
+
* in the physical 3d space, as a result, by default, all annotations that are
|
|
55
|
+
* drawing in the same frameOfReference will get shared between viewports that
|
|
56
|
+
* are in the same frameOfReference.
|
|
57
|
+
*
|
|
58
|
+
* The resulting annotation's data (statistics) and metadata (the
|
|
59
|
+
* state of the viewport while drawing was happening) will get added to the
|
|
60
|
+
* ToolState manager and can be accessed from the ToolState by calling getAnnotations
|
|
61
|
+
* or similar methods.
|
|
62
|
+
*
|
|
63
|
+
* ```js
|
|
64
|
+
* cornerstoneTools.addTool(HeightTool)
|
|
65
|
+
*
|
|
66
|
+
* const toolGroup = ToolGroupManager.createToolGroup('toolGroupId')
|
|
67
|
+
*
|
|
68
|
+
* toolGroup.addTool(HeightTool.toolName)
|
|
69
|
+
*
|
|
70
|
+
* toolGroup.addViewport('viewportId', 'renderingEngineId')
|
|
71
|
+
*
|
|
72
|
+
* toolGroup.setToolActive(HeightTool.toolName, {
|
|
73
|
+
* bindings: [
|
|
74
|
+
* {
|
|
75
|
+
* mouseButton: MouseBindings.Primary, // Left Click
|
|
76
|
+
* },
|
|
77
|
+
* ],
|
|
78
|
+
* })
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* Read more in the Docs section of the website.
|
|
82
|
+
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
class HeightTool extends AnnotationTool {
|
|
86
|
+
static toolName;
|
|
87
|
+
|
|
88
|
+
public touchDragCallback: any;
|
|
89
|
+
public mouseDragCallback: any;
|
|
90
|
+
_throttledCalculateCachedStats: any;
|
|
91
|
+
editData: {
|
|
92
|
+
annotation: any;
|
|
93
|
+
viewportIdsToRender: string[];
|
|
94
|
+
handleIndex?: number;
|
|
95
|
+
movingTextBox?: boolean;
|
|
96
|
+
newAnnotation?: boolean;
|
|
97
|
+
hasMoved?: boolean;
|
|
98
|
+
} | null;
|
|
99
|
+
isDrawing: boolean;
|
|
100
|
+
isHandleOutsideImage: boolean;
|
|
101
|
+
|
|
102
|
+
//Lines to generate height
|
|
103
|
+
endfirstLine: Types.Point2;
|
|
104
|
+
endsecondLine: Types.Point2;
|
|
105
|
+
//Middle lines:
|
|
106
|
+
midX: number;
|
|
107
|
+
|
|
108
|
+
constructor(
|
|
109
|
+
toolProps: PublicToolProps = {},
|
|
110
|
+
defaultToolProps: ToolProps = {
|
|
111
|
+
supportedInteractionTypes: ['Mouse', 'Touch'],
|
|
112
|
+
configuration: {
|
|
113
|
+
preventHandleOutsideImage: false,
|
|
114
|
+
getTextLines: defaultGetTextLines,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
) {
|
|
118
|
+
super(toolProps, defaultToolProps);
|
|
119
|
+
|
|
120
|
+
this._throttledCalculateCachedStats = throttle(
|
|
121
|
+
this._calculateCachedStats,
|
|
122
|
+
100,
|
|
123
|
+
{ trailing: true }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Based on the current position of the mouse and the current imageId to create
|
|
129
|
+
* a Length Annotation and stores it in the annotationManager
|
|
130
|
+
*
|
|
131
|
+
* @param evt - EventTypes.NormalizedMouseEventType
|
|
132
|
+
* @returns The annotation object.
|
|
133
|
+
*
|
|
134
|
+
*/
|
|
135
|
+
addNewAnnotation = (
|
|
136
|
+
evt: EventTypes.InteractionEventType
|
|
137
|
+
): LengthAnnotation => {
|
|
138
|
+
const eventDetail = evt.detail;
|
|
139
|
+
const { currentPoints, element } = eventDetail;
|
|
140
|
+
const worldPos = currentPoints.world;
|
|
141
|
+
const enabledElement = getEnabledElement(element);
|
|
142
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
143
|
+
|
|
144
|
+
hideElementCursor(element);
|
|
145
|
+
this.isDrawing = true;
|
|
146
|
+
|
|
147
|
+
const {
|
|
148
|
+
viewPlaneNormal,
|
|
149
|
+
viewUp,
|
|
150
|
+
position: cameraPosition,
|
|
151
|
+
} = viewport.getCamera();
|
|
152
|
+
const referencedImageId = this.getReferencedImageId(
|
|
153
|
+
viewport,
|
|
154
|
+
worldPos,
|
|
155
|
+
viewPlaneNormal,
|
|
156
|
+
viewUp
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const annotation = {
|
|
160
|
+
highlighted: true,
|
|
161
|
+
invalidated: true,
|
|
162
|
+
metadata: {
|
|
163
|
+
...viewport.getViewReference({ points: [worldPos] }),
|
|
164
|
+
toolName: this.getToolName(),
|
|
165
|
+
referencedImageId,
|
|
166
|
+
viewUp,
|
|
167
|
+
cameraPosition,
|
|
168
|
+
},
|
|
169
|
+
data: {
|
|
170
|
+
handles: {
|
|
171
|
+
points: [<Types.Point3>[...worldPos], <Types.Point3>[...worldPos]],
|
|
172
|
+
activeHandleIndex: null,
|
|
173
|
+
textBox: {
|
|
174
|
+
hasMoved: false,
|
|
175
|
+
worldPosition: <Types.Point3>[0, 0, 0],
|
|
176
|
+
worldBoundingBox: {
|
|
177
|
+
topLeft: <Types.Point3>[0, 0, 0],
|
|
178
|
+
topRight: <Types.Point3>[0, 0, 0],
|
|
179
|
+
bottomLeft: <Types.Point3>[0, 0, 0],
|
|
180
|
+
bottomRight: <Types.Point3>[0, 0, 0],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
label: '',
|
|
185
|
+
cachedStats: {},
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
addAnnotation(annotation, element);
|
|
190
|
+
|
|
191
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
192
|
+
element,
|
|
193
|
+
this.getToolName()
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
this.editData = {
|
|
197
|
+
annotation,
|
|
198
|
+
viewportIdsToRender,
|
|
199
|
+
handleIndex: 1,
|
|
200
|
+
movingTextBox: false,
|
|
201
|
+
newAnnotation: true,
|
|
202
|
+
hasMoved: false,
|
|
203
|
+
};
|
|
204
|
+
this._activateDraw(element);
|
|
205
|
+
|
|
206
|
+
evt.preventDefault();
|
|
207
|
+
|
|
208
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
209
|
+
|
|
210
|
+
return annotation;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* It returns if the canvas point is near the provided height annotation in the provided
|
|
215
|
+
* element or not. A proximity is passed to the function to determine the
|
|
216
|
+
* proximity of the point to the annotation in number of pixels.
|
|
217
|
+
*
|
|
218
|
+
* @param element - HTML Element
|
|
219
|
+
* @param annotation - Annotation
|
|
220
|
+
* @param canvasCoords - Canvas coordinates
|
|
221
|
+
* @param proximity - Proximity to tool to consider
|
|
222
|
+
* @returns Boolean, whether the canvas point is near tool
|
|
223
|
+
*/
|
|
224
|
+
isPointNearTool = (
|
|
225
|
+
element: HTMLDivElement,
|
|
226
|
+
annotation: LengthAnnotation,
|
|
227
|
+
canvasCoords: Types.Point2,
|
|
228
|
+
proximity: number
|
|
229
|
+
): boolean => {
|
|
230
|
+
const enabledElement = getEnabledElement(element);
|
|
231
|
+
const { viewport } = enabledElement;
|
|
232
|
+
const { data } = annotation;
|
|
233
|
+
const [point1, point2] = data.handles.points;
|
|
234
|
+
const canvasPoint1 = viewport.worldToCanvas(point1);
|
|
235
|
+
const canvasPoint2 = viewport.worldToCanvas(point2);
|
|
236
|
+
|
|
237
|
+
const line = {
|
|
238
|
+
start: {
|
|
239
|
+
x: canvasPoint1[0],
|
|
240
|
+
y: canvasPoint1[1],
|
|
241
|
+
},
|
|
242
|
+
end: {
|
|
243
|
+
x: canvasPoint2[0],
|
|
244
|
+
y: canvasPoint2[1],
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const distanceToPoint = lineSegment.distanceToPoint(
|
|
249
|
+
[line.start.x, line.start.y],
|
|
250
|
+
[line.end.x, line.end.y],
|
|
251
|
+
[canvasCoords[0], canvasCoords[1]]
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
if (distanceToPoint <= proximity) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return false;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
toolSelectedCallback = (
|
|
262
|
+
evt: EventTypes.InteractionEventType,
|
|
263
|
+
annotation: LengthAnnotation
|
|
264
|
+
): void => {
|
|
265
|
+
const eventDetail = evt.detail;
|
|
266
|
+
const { element } = eventDetail;
|
|
267
|
+
|
|
268
|
+
annotation.highlighted = true;
|
|
269
|
+
|
|
270
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
271
|
+
element,
|
|
272
|
+
this.getToolName()
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
this.editData = {
|
|
276
|
+
annotation,
|
|
277
|
+
viewportIdsToRender,
|
|
278
|
+
movingTextBox: false,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
this._activateModify(element);
|
|
282
|
+
|
|
283
|
+
hideElementCursor(element);
|
|
284
|
+
|
|
285
|
+
const enabledElement = getEnabledElement(element);
|
|
286
|
+
const { renderingEngine } = enabledElement;
|
|
287
|
+
|
|
288
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
289
|
+
|
|
290
|
+
evt.preventDefault();
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
handleSelectedCallback(
|
|
294
|
+
evt: EventTypes.InteractionEventType,
|
|
295
|
+
annotation: LengthAnnotation,
|
|
296
|
+
handle: ToolHandle
|
|
297
|
+
): void {
|
|
298
|
+
const eventDetail = evt.detail;
|
|
299
|
+
const { element } = eventDetail;
|
|
300
|
+
const { data } = annotation;
|
|
301
|
+
|
|
302
|
+
annotation.highlighted = true;
|
|
303
|
+
|
|
304
|
+
let movingTextBox = false;
|
|
305
|
+
let handleIndex;
|
|
306
|
+
|
|
307
|
+
if ((handle as TextBoxHandle).worldPosition) {
|
|
308
|
+
movingTextBox = true;
|
|
309
|
+
} else {
|
|
310
|
+
handleIndex = data.handles.points.findIndex((p) => p === handle);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Find viewports to render on drag.
|
|
314
|
+
const viewportIdsToRender = getViewportIdsWithToolToRender(
|
|
315
|
+
element,
|
|
316
|
+
this.getToolName()
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
this.editData = {
|
|
320
|
+
annotation,
|
|
321
|
+
viewportIdsToRender,
|
|
322
|
+
handleIndex,
|
|
323
|
+
movingTextBox,
|
|
324
|
+
};
|
|
325
|
+
this._activateModify(element);
|
|
326
|
+
|
|
327
|
+
hideElementCursor(element);
|
|
328
|
+
|
|
329
|
+
const enabledElement = getEnabledElement(element);
|
|
330
|
+
const { renderingEngine } = enabledElement;
|
|
331
|
+
|
|
332
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
333
|
+
|
|
334
|
+
evt.preventDefault();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
_endCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
338
|
+
const eventDetail = evt.detail;
|
|
339
|
+
const { element } = eventDetail;
|
|
340
|
+
|
|
341
|
+
const { annotation, viewportIdsToRender, newAnnotation, hasMoved } =
|
|
342
|
+
this.editData;
|
|
343
|
+
const { data } = annotation;
|
|
344
|
+
|
|
345
|
+
if (newAnnotation && !hasMoved) {
|
|
346
|
+
// when user starts the drawing by click, and moving the mouse, instead
|
|
347
|
+
// of click and drag
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
data.handles.activeHandleIndex = null;
|
|
352
|
+
|
|
353
|
+
this._deactivateModify(element);
|
|
354
|
+
this._deactivateDraw(element);
|
|
355
|
+
resetElementCursor(element);
|
|
356
|
+
|
|
357
|
+
const enabledElement = getEnabledElement(element);
|
|
358
|
+
const { renderingEngine } = enabledElement;
|
|
359
|
+
|
|
360
|
+
if (
|
|
361
|
+
this.isHandleOutsideImage &&
|
|
362
|
+
this.configuration.preventHandleOutsideImage
|
|
363
|
+
) {
|
|
364
|
+
removeAnnotation(annotation.annotationUID);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
368
|
+
|
|
369
|
+
if (newAnnotation) {
|
|
370
|
+
triggerAnnotationCompleted(annotation);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
this.editData = null;
|
|
374
|
+
this.isDrawing = false;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
_dragCallback = (evt: EventTypes.InteractionEventType): void => {
|
|
378
|
+
this.isDrawing = true;
|
|
379
|
+
const eventDetail = evt.detail;
|
|
380
|
+
const { element } = eventDetail;
|
|
381
|
+
|
|
382
|
+
const { annotation, viewportIdsToRender, handleIndex, movingTextBox } =
|
|
383
|
+
this.editData;
|
|
384
|
+
const { data } = annotation;
|
|
385
|
+
|
|
386
|
+
if (movingTextBox) {
|
|
387
|
+
// Drag mode - moving text box
|
|
388
|
+
const { deltaPoints } = eventDetail as EventTypes.MouseDragEventDetail;
|
|
389
|
+
const worldPosDelta = deltaPoints.world;
|
|
390
|
+
|
|
391
|
+
const { textBox } = data.handles;
|
|
392
|
+
const { worldPosition } = textBox;
|
|
393
|
+
|
|
394
|
+
worldPosition[0] += worldPosDelta[0];
|
|
395
|
+
worldPosition[1] += worldPosDelta[1];
|
|
396
|
+
worldPosition[2] += worldPosDelta[2];
|
|
397
|
+
|
|
398
|
+
textBox.hasMoved = true;
|
|
399
|
+
} else if (handleIndex === undefined) {
|
|
400
|
+
// Drag mode - moving handle
|
|
401
|
+
const { deltaPoints } = eventDetail as EventTypes.MouseDragEventDetail;
|
|
402
|
+
const worldPosDelta = deltaPoints.world;
|
|
403
|
+
|
|
404
|
+
const points = data.handles.points;
|
|
405
|
+
|
|
406
|
+
points.forEach((point) => {
|
|
407
|
+
point[0] += worldPosDelta[0];
|
|
408
|
+
point[1] += worldPosDelta[1];
|
|
409
|
+
point[2] += worldPosDelta[2];
|
|
410
|
+
});
|
|
411
|
+
annotation.invalidated = true;
|
|
412
|
+
} else {
|
|
413
|
+
// Move mode - after double click, and mouse move to draw
|
|
414
|
+
const { currentPoints } = eventDetail;
|
|
415
|
+
const worldPos = currentPoints.world;
|
|
416
|
+
|
|
417
|
+
data.handles.points[handleIndex] = [...worldPos];
|
|
418
|
+
annotation.invalidated = true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.editData.hasMoved = true;
|
|
422
|
+
|
|
423
|
+
const enabledElement = getEnabledElement(element);
|
|
424
|
+
const { renderingEngine } = enabledElement;
|
|
425
|
+
|
|
426
|
+
triggerAnnotationRenderForViewportIds(renderingEngine, viewportIdsToRender);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
cancel = (element: HTMLDivElement) => {
|
|
430
|
+
// If it is mid-draw or mid-modify
|
|
431
|
+
if (this.isDrawing) {
|
|
432
|
+
this.isDrawing = false;
|
|
433
|
+
this._deactivateDraw(element);
|
|
434
|
+
this._deactivateModify(element);
|
|
435
|
+
resetElementCursor(element);
|
|
436
|
+
|
|
437
|
+
const { annotation, viewportIdsToRender, newAnnotation } = this.editData;
|
|
438
|
+
const { data } = annotation;
|
|
439
|
+
|
|
440
|
+
annotation.highlighted = false;
|
|
441
|
+
data.handles.activeHandleIndex = null;
|
|
442
|
+
|
|
443
|
+
const enabledElement = getEnabledElement(element);
|
|
444
|
+
const { renderingEngine } = enabledElement;
|
|
445
|
+
|
|
446
|
+
triggerAnnotationRenderForViewportIds(
|
|
447
|
+
renderingEngine,
|
|
448
|
+
viewportIdsToRender
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
if (newAnnotation) {
|
|
452
|
+
triggerAnnotationCompleted(annotation);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
this.editData = null;
|
|
456
|
+
return annotation.annotationUID;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
_activateModify = (element: HTMLDivElement) => {
|
|
461
|
+
state.isInteractingWithTool = true;
|
|
462
|
+
|
|
463
|
+
element.addEventListener(
|
|
464
|
+
Events.MOUSE_UP,
|
|
465
|
+
this._endCallback as EventListener
|
|
466
|
+
);
|
|
467
|
+
element.addEventListener(
|
|
468
|
+
Events.MOUSE_DRAG,
|
|
469
|
+
this._dragCallback as EventListener
|
|
470
|
+
);
|
|
471
|
+
element.addEventListener(
|
|
472
|
+
Events.MOUSE_CLICK,
|
|
473
|
+
this._endCallback as EventListener
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
element.addEventListener(
|
|
477
|
+
Events.TOUCH_END,
|
|
478
|
+
this._endCallback as EventListener
|
|
479
|
+
);
|
|
480
|
+
element.addEventListener(
|
|
481
|
+
Events.TOUCH_DRAG,
|
|
482
|
+
this._dragCallback as EventListener
|
|
483
|
+
);
|
|
484
|
+
element.addEventListener(
|
|
485
|
+
Events.TOUCH_TAP,
|
|
486
|
+
this._endCallback as EventListener
|
|
487
|
+
);
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
_deactivateModify = (element: HTMLDivElement) => {
|
|
491
|
+
state.isInteractingWithTool = false;
|
|
492
|
+
|
|
493
|
+
element.removeEventListener(
|
|
494
|
+
Events.MOUSE_UP,
|
|
495
|
+
this._endCallback as EventListener
|
|
496
|
+
);
|
|
497
|
+
element.removeEventListener(
|
|
498
|
+
Events.MOUSE_DRAG,
|
|
499
|
+
this._dragCallback as EventListener
|
|
500
|
+
);
|
|
501
|
+
element.removeEventListener(
|
|
502
|
+
Events.MOUSE_CLICK,
|
|
503
|
+
this._endCallback as EventListener
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
element.removeEventListener(
|
|
507
|
+
Events.TOUCH_END,
|
|
508
|
+
this._endCallback as EventListener
|
|
509
|
+
);
|
|
510
|
+
element.removeEventListener(
|
|
511
|
+
Events.TOUCH_DRAG,
|
|
512
|
+
this._dragCallback as EventListener
|
|
513
|
+
);
|
|
514
|
+
element.removeEventListener(
|
|
515
|
+
Events.TOUCH_TAP,
|
|
516
|
+
this._endCallback as EventListener
|
|
517
|
+
);
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
_activateDraw = (element: HTMLDivElement) => {
|
|
521
|
+
state.isInteractingWithTool = true;
|
|
522
|
+
|
|
523
|
+
element.addEventListener(
|
|
524
|
+
Events.MOUSE_UP,
|
|
525
|
+
this._endCallback as EventListener
|
|
526
|
+
);
|
|
527
|
+
element.addEventListener(
|
|
528
|
+
Events.MOUSE_DRAG,
|
|
529
|
+
this._dragCallback as EventListener
|
|
530
|
+
);
|
|
531
|
+
element.addEventListener(
|
|
532
|
+
Events.MOUSE_MOVE,
|
|
533
|
+
this._dragCallback as EventListener
|
|
534
|
+
);
|
|
535
|
+
element.addEventListener(
|
|
536
|
+
Events.MOUSE_CLICK,
|
|
537
|
+
this._endCallback as EventListener
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
element.addEventListener(
|
|
541
|
+
Events.TOUCH_END,
|
|
542
|
+
this._endCallback as EventListener
|
|
543
|
+
);
|
|
544
|
+
element.addEventListener(
|
|
545
|
+
Events.TOUCH_DRAG,
|
|
546
|
+
this._dragCallback as EventListener
|
|
547
|
+
);
|
|
548
|
+
element.addEventListener(
|
|
549
|
+
Events.TOUCH_TAP,
|
|
550
|
+
this._endCallback as EventListener
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
_deactivateDraw = (element: HTMLDivElement) => {
|
|
555
|
+
state.isInteractingWithTool = false;
|
|
556
|
+
|
|
557
|
+
element.removeEventListener(
|
|
558
|
+
Events.MOUSE_UP,
|
|
559
|
+
this._endCallback as EventListener
|
|
560
|
+
);
|
|
561
|
+
element.removeEventListener(
|
|
562
|
+
Events.MOUSE_DRAG,
|
|
563
|
+
this._dragCallback as EventListener
|
|
564
|
+
);
|
|
565
|
+
element.removeEventListener(
|
|
566
|
+
Events.MOUSE_MOVE,
|
|
567
|
+
this._dragCallback as EventListener
|
|
568
|
+
);
|
|
569
|
+
element.removeEventListener(
|
|
570
|
+
Events.MOUSE_CLICK,
|
|
571
|
+
this._endCallback as EventListener
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
element.removeEventListener(
|
|
575
|
+
Events.TOUCH_END,
|
|
576
|
+
this._endCallback as EventListener
|
|
577
|
+
);
|
|
578
|
+
element.removeEventListener(
|
|
579
|
+
Events.TOUCH_DRAG,
|
|
580
|
+
this._dragCallback as EventListener
|
|
581
|
+
);
|
|
582
|
+
element.removeEventListener(
|
|
583
|
+
Events.TOUCH_TAP,
|
|
584
|
+
this._endCallback as EventListener
|
|
585
|
+
);
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* it is used to draw the height annotation in each
|
|
590
|
+
* request animation frame. It calculates the updated cached statistics if
|
|
591
|
+
* data is invalidated and cache it.
|
|
592
|
+
*
|
|
593
|
+
* @param enabledElement - The Cornerstone's enabledElement.
|
|
594
|
+
* @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
|
|
595
|
+
*/
|
|
596
|
+
renderAnnotation = (
|
|
597
|
+
enabledElement: Types.IEnabledElement,
|
|
598
|
+
svgDrawingHelper: SVGDrawingHelper
|
|
599
|
+
): boolean => {
|
|
600
|
+
let renderStatus = false;
|
|
601
|
+
const { viewport } = enabledElement;
|
|
602
|
+
const { element } = viewport;
|
|
603
|
+
|
|
604
|
+
let annotations = getAnnotations(this.getToolName(), element);
|
|
605
|
+
|
|
606
|
+
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
|
607
|
+
if (!annotations?.length) {
|
|
608
|
+
return renderStatus;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
annotations = this.filterInteractableAnnotationsForElement(
|
|
612
|
+
element,
|
|
613
|
+
annotations
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
if (!annotations?.length) {
|
|
617
|
+
return renderStatus;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const targetId = this.getTargetId(viewport);
|
|
621
|
+
const renderingEngine = viewport.getRenderingEngine();
|
|
622
|
+
|
|
623
|
+
const styleSpecifier: StyleSpecifier = {
|
|
624
|
+
toolGroupId: this.toolGroupId,
|
|
625
|
+
toolName: this.getToolName(),
|
|
626
|
+
viewportId: enabledElement.viewport.id,
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// Draw SVG
|
|
630
|
+
for (let i = 0; i < annotations.length; i++) {
|
|
631
|
+
const annotation = annotations[i] as LengthAnnotation;
|
|
632
|
+
const { annotationUID, data } = annotation;
|
|
633
|
+
const { points, activeHandleIndex } = data.handles;
|
|
634
|
+
|
|
635
|
+
styleSpecifier.annotationUID = annotationUID;
|
|
636
|
+
|
|
637
|
+
const { color, lineWidth, lineDash, shadow } = this.getAnnotationStyle({
|
|
638
|
+
annotation,
|
|
639
|
+
styleSpecifier,
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
643
|
+
|
|
644
|
+
let activeHandleCanvasCoords;
|
|
645
|
+
|
|
646
|
+
// If cachedStats does not exist, or the unit is missing (as part of import/hydration etc.),
|
|
647
|
+
// force to recalculate the stats from the points
|
|
648
|
+
if (
|
|
649
|
+
!data.cachedStats[targetId] ||
|
|
650
|
+
data.cachedStats[targetId].unit == null
|
|
651
|
+
) {
|
|
652
|
+
data.cachedStats[targetId] = {
|
|
653
|
+
length: null,
|
|
654
|
+
unit: null,
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
this._calculateCachedStats(annotation, renderingEngine, enabledElement);
|
|
658
|
+
} else if (annotation.invalidated) {
|
|
659
|
+
this._throttledCalculateCachedStats(
|
|
660
|
+
annotation,
|
|
661
|
+
renderingEngine,
|
|
662
|
+
enabledElement
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (!isAnnotationVisible(annotationUID)) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (
|
|
671
|
+
!isAnnotationLocked(annotation) &&
|
|
672
|
+
!this.editData &&
|
|
673
|
+
activeHandleIndex !== null
|
|
674
|
+
) {
|
|
675
|
+
// Not locked or creating and hovering over handle, so render handle.
|
|
676
|
+
activeHandleCanvasCoords = [canvasCoordinates[activeHandleIndex]];
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (activeHandleCanvasCoords) {
|
|
680
|
+
const handleGroupUID = '0';
|
|
681
|
+
|
|
682
|
+
drawHandlesSvg(
|
|
683
|
+
svgDrawingHelper,
|
|
684
|
+
annotationUID,
|
|
685
|
+
handleGroupUID,
|
|
686
|
+
canvasCoordinates,
|
|
687
|
+
{
|
|
688
|
+
color,
|
|
689
|
+
lineDash,
|
|
690
|
+
lineWidth,
|
|
691
|
+
}
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const heightUID = '0';
|
|
696
|
+
|
|
697
|
+
//Draw Height:
|
|
698
|
+
drawHeightSvg(
|
|
699
|
+
svgDrawingHelper,
|
|
700
|
+
annotationUID,
|
|
701
|
+
heightUID,
|
|
702
|
+
canvasCoordinates[0],
|
|
703
|
+
canvasCoordinates[1],
|
|
704
|
+
{
|
|
705
|
+
color,
|
|
706
|
+
width: lineWidth,
|
|
707
|
+
lineDash: lineDash,
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
renderStatus = true;
|
|
712
|
+
|
|
713
|
+
// If rendering engine has been destroyed while rendering
|
|
714
|
+
if (!viewport.getRenderingEngine()) {
|
|
715
|
+
console.warn('Rendering Engine has been destroyed');
|
|
716
|
+
return renderStatus;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const options = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
|
720
|
+
if (!options.visibility) {
|
|
721
|
+
data.handles.textBox = {
|
|
722
|
+
hasMoved: false,
|
|
723
|
+
worldPosition: <Types.Point3>[0, 0, 0],
|
|
724
|
+
worldBoundingBox: {
|
|
725
|
+
topLeft: <Types.Point3>[0, 0, 0],
|
|
726
|
+
topRight: <Types.Point3>[0, 0, 0],
|
|
727
|
+
bottomLeft: <Types.Point3>[0, 0, 0],
|
|
728
|
+
bottomRight: <Types.Point3>[0, 0, 0],
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const textLines = this.configuration.getTextLines(data, targetId);
|
|
735
|
+
|
|
736
|
+
// Need to update to sync with annotation while unlinked/not moved
|
|
737
|
+
if (!data.handles.textBox.hasMoved) {
|
|
738
|
+
const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);
|
|
739
|
+
|
|
740
|
+
data.handles.textBox.worldPosition =
|
|
741
|
+
viewport.canvasToWorld(canvasTextBoxCoords);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const textBoxPosition = viewport.worldToCanvas(
|
|
745
|
+
data.handles.textBox.worldPosition
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
const textBoxUID = '1';
|
|
749
|
+
const boundingBox = drawLinkedTextBoxSvg(
|
|
750
|
+
svgDrawingHelper,
|
|
751
|
+
annotationUID,
|
|
752
|
+
textBoxUID,
|
|
753
|
+
textLines,
|
|
754
|
+
textBoxPosition,
|
|
755
|
+
canvasCoordinates,
|
|
756
|
+
{},
|
|
757
|
+
options
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
const { x: left, y: top, width, height } = boundingBox;
|
|
761
|
+
|
|
762
|
+
data.handles.textBox.worldBoundingBox = {
|
|
763
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
764
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
765
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
766
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return renderStatus;
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
_calculateHeight(pos1, pos2) {
|
|
774
|
+
const dx = pos2[0] - pos1[0];
|
|
775
|
+
const dy = pos2[1] - pos1[1];
|
|
776
|
+
const dz = pos2[2] - pos1[2];
|
|
777
|
+
//SAGITAL X alway 0
|
|
778
|
+
//CORONAL Y alway 0
|
|
779
|
+
//AXIAL Z alway 0
|
|
780
|
+
|
|
781
|
+
//SAGITAL:
|
|
782
|
+
if (dx == 0) {
|
|
783
|
+
//SAGITAL when it reaches 0 takes the measurement of Y, to correct it we return 0.
|
|
784
|
+
if (dy != 0) {
|
|
785
|
+
//SAGITAL use Z:
|
|
786
|
+
return Math.abs(dz);
|
|
787
|
+
} else {
|
|
788
|
+
return 0;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
//SAGITAL AND CORONAL use Z:
|
|
792
|
+
//CORONAL:
|
|
793
|
+
else if (dy == 0) {
|
|
794
|
+
//CORONAL use Z:
|
|
795
|
+
return Math.abs(dz);
|
|
796
|
+
}
|
|
797
|
+
//AXIAL
|
|
798
|
+
else if (dz == 0) {
|
|
799
|
+
//AXIAL use Y:
|
|
800
|
+
return Math.abs(dy);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
_calculateCachedStats(annotation, renderingEngine, enabledElement) {
|
|
805
|
+
const data = annotation.data;
|
|
806
|
+
const { element } = enabledElement.viewport;
|
|
807
|
+
|
|
808
|
+
const worldPos1 = data.handles.points[0];
|
|
809
|
+
const worldPos2 = data.handles.points[1];
|
|
810
|
+
const { cachedStats } = data;
|
|
811
|
+
const targetIds = Object.keys(cachedStats);
|
|
812
|
+
|
|
813
|
+
// TODO clean up, this doesn't need a length per volume, it has no stats derived from volumes.
|
|
814
|
+
|
|
815
|
+
for (let i = 0; i < targetIds.length; i++) {
|
|
816
|
+
const targetId = targetIds[i];
|
|
817
|
+
|
|
818
|
+
const image = this.getTargetIdImage(targetId, renderingEngine);
|
|
819
|
+
|
|
820
|
+
// If image does not exists for the targetId, skip. This can be due
|
|
821
|
+
// to various reasons such as if the target was a volumeViewport, and
|
|
822
|
+
// the volumeViewport has been decached in the meantime.
|
|
823
|
+
if (!image) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const { imageData, dimensions } = image;
|
|
828
|
+
|
|
829
|
+
const index1 = transformWorldToIndex(imageData, worldPos1);
|
|
830
|
+
const index2 = transformWorldToIndex(imageData, worldPos2);
|
|
831
|
+
const handles = [index1, index2];
|
|
832
|
+
const { scale, units } = getCalibratedLengthUnitsAndScale(image, handles);
|
|
833
|
+
|
|
834
|
+
const height = this._calculateHeight(worldPos1, worldPos2) / scale;
|
|
835
|
+
|
|
836
|
+
this._isInsideVolume(index1, index2, dimensions)
|
|
837
|
+
? (this.isHandleOutsideImage = false)
|
|
838
|
+
: (this.isHandleOutsideImage = true);
|
|
839
|
+
|
|
840
|
+
// TODO -> Do we instead want to clip to the bounds of the volume and only include that portion?
|
|
841
|
+
// Seems like a lot of work for an unrealistic case. At the moment bail out of stat calculation if either
|
|
842
|
+
// corner is off the canvas.
|
|
843
|
+
|
|
844
|
+
// todo: add insideVolume calculation, for removing tool if outside
|
|
845
|
+
cachedStats[targetId] = {
|
|
846
|
+
height,
|
|
847
|
+
unit: units,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
annotation.invalidated = false;
|
|
852
|
+
|
|
853
|
+
// Dispatching annotation modified
|
|
854
|
+
triggerAnnotationModified(annotation, element);
|
|
855
|
+
|
|
856
|
+
return cachedStats;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
_isInsideVolume(index1, index2, dimensions) {
|
|
860
|
+
return (
|
|
861
|
+
csUtils.indexWithinDimensions(index1, dimensions) &&
|
|
862
|
+
csUtils.indexWithinDimensions(index2, dimensions)
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function defaultGetTextLines(data, targetId): string[] {
|
|
868
|
+
const cachedVolumeStats = data.cachedStats[targetId];
|
|
869
|
+
const { height, unit } = cachedVolumeStats;
|
|
870
|
+
|
|
871
|
+
// Can be null on load
|
|
872
|
+
if (height === undefined || height === null || isNaN(height)) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const textLines = [`${roundNumber(height)} ${unit}`];
|
|
877
|
+
|
|
878
|
+
return textLines;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
HeightTool.toolName = 'Height';
|
|
882
|
+
export default HeightTool;
|