@cornerstonejs/tools 1.16.6 → 1.17.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "1.16.6",
3
+ "version": "1.17.0",
4
4
  "description": "Cornerstone3D Tools",
5
5
  "main": "dist/umd/index.js",
6
6
  "types": "dist/esm/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  "webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js"
30
30
  },
31
31
  "dependencies": {
32
- "@cornerstonejs/core": "^1.16.6",
32
+ "@cornerstonejs/core": "^1.17.0",
33
33
  "lodash.clonedeep": "4.5.0",
34
34
  "lodash.get": "^4.4.2"
35
35
  },
@@ -52,5 +52,5 @@
52
52
  "type": "individual",
53
53
  "url": "https://ohif.org/donate"
54
54
  },
55
- "gitHead": "d0d2fac80581648681521e4ddb6a6d9aad2087f9"
55
+ "gitHead": "1981588ef6468e8482973c0bb2048f09bbc6c113"
56
56
  }
package/src/index.ts CHANGED
@@ -58,6 +58,7 @@ import {
58
58
  ReferenceLines,
59
59
  PaintFillTool,
60
60
  ScaleOverlayTool,
61
+ OverlayGridTool,
61
62
  } from './tools';
62
63
 
63
64
  import * as Enums from './enums';
@@ -88,6 +89,7 @@ export {
88
89
  LengthTool,
89
90
  CrosshairsTool,
90
91
  ReferenceLinesTool,
92
+ OverlayGridTool,
91
93
  ProbeTool,
92
94
  RectangleROITool,
93
95
  EllipticalROITool,
@@ -0,0 +1,357 @@
1
+ import { vec3 } from 'gl-matrix';
2
+ import {
3
+ metaData,
4
+ CONSTANTS,
5
+ getRenderingEngine,
6
+ utilities as csUtils,
7
+ } from '@cornerstonejs/core';
8
+ import type { Types } from '@cornerstonejs/core';
9
+
10
+ import {
11
+ addAnnotation,
12
+ getAnnotations,
13
+ } from '../stateManagement/annotation/annotationState';
14
+
15
+ import { getToolGroup } from '../store/ToolGroupManager';
16
+
17
+ import { drawLine as drawLineSvg } from '../drawingSvg';
18
+ import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds';
19
+
20
+ import {
21
+ PublicToolProps,
22
+ ToolProps,
23
+ SVGDrawingHelper,
24
+ Annotation,
25
+ } from '../types';
26
+ import { StyleSpecifier } from '../types/AnnotationStyle';
27
+ import AnnotationDisplayTool from './base/AnnotationDisplayTool';
28
+
29
+ const { EPSILON } = CONSTANTS;
30
+
31
+ export interface OverlayGridAnnotation extends Annotation {
32
+ data: {
33
+ viewportData: Map<string, object>;
34
+ pointSets: Array<object>;
35
+ };
36
+ }
37
+
38
+ /**
39
+ * @public
40
+ */
41
+ class OverlayGridTool extends AnnotationDisplayTool {
42
+ static toolName;
43
+
44
+ public touchDragCallback: any;
45
+ public mouseDragCallback: any;
46
+ _throttledCalculateCachedStats: any;
47
+ isDrawing: boolean;
48
+ isHandleOutsideImage: boolean;
49
+
50
+ constructor(
51
+ toolProps: PublicToolProps = {},
52
+ defaultToolProps: ToolProps = {
53
+ supportedInteractionTypes: ['Mouse', 'Touch'],
54
+ configuration: {
55
+ sourceImageIds: [],
56
+ },
57
+ }
58
+ ) {
59
+ super(toolProps, defaultToolProps);
60
+ }
61
+
62
+ onSetToolEnabled = (): void => {
63
+ this._init();
64
+ };
65
+
66
+ onSetToolActive = (): void => {
67
+ this._init();
68
+ };
69
+
70
+ _init = (): void => {
71
+ const sourceImageIds = this.configuration.sourceImageIds;
72
+ if (!sourceImageIds?.length) {
73
+ console.warn(
74
+ 'OverlayGridTool: No sourceImageIds provided in configuration'
75
+ );
76
+ return;
77
+ }
78
+
79
+ const imagePlaneModule = metaData.get(
80
+ 'imagePlaneModule',
81
+ sourceImageIds[0]
82
+ );
83
+
84
+ if (!imagePlaneModule) {
85
+ console.warn(
86
+ 'OverlayGridTool: No imagePlaneModule found for sourceImageIds'
87
+ );
88
+ return;
89
+ }
90
+
91
+ const { frameOfReferenceUID } = imagePlaneModule;
92
+
93
+ const viewportsInfo = getToolGroup(this.toolGroupId).viewportsInfo;
94
+
95
+ if (!viewportsInfo?.length) {
96
+ console.warn('OverlayGridTool: No viewports found');
97
+ return;
98
+ }
99
+
100
+ const annotations = getAnnotations(this.getToolName(), frameOfReferenceUID);
101
+
102
+ if (!annotations?.length) {
103
+ const pointSets = sourceImageIds.map((id) => {
104
+ // check if pointSets for the imageId was calculated. If not calculate and store
105
+ return this.calculateImageIdPointSets(id);
106
+ });
107
+
108
+ const newAnnotation: OverlayGridAnnotation = {
109
+ highlighted: true,
110
+ invalidated: true,
111
+ metadata: {
112
+ toolName: this.getToolName(),
113
+ FrameOfReferenceUID: frameOfReferenceUID,
114
+ referencedImageId: null,
115
+ },
116
+ data: {
117
+ viewportData: new Map(),
118
+ pointSets,
119
+ },
120
+ };
121
+
122
+ addAnnotation(newAnnotation, frameOfReferenceUID);
123
+ }
124
+
125
+ triggerAnnotationRenderForViewportIds(
126
+ getRenderingEngine(viewportsInfo[0].renderingEngineId),
127
+ viewportsInfo.map(({ viewportId }) => viewportId)
128
+ );
129
+ };
130
+
131
+ /**
132
+ * Calculates the point sets based on the image corners relative to an imageId
133
+ * @param imageId - The imageId to calculate the point sets for
134
+ * @returns
135
+ */
136
+ calculateImageIdPointSets = (imageId: string) => {
137
+ const {
138
+ imagePositionPatient,
139
+ rows,
140
+ columns,
141
+ rowCosines,
142
+ columnCosines,
143
+ rowPixelSpacing,
144
+ columnPixelSpacing,
145
+ } = metaData.get('imagePlaneModule', imageId);
146
+
147
+ // top left world, top right world, bottom right world, bottom left world
148
+ const topLeft = <Types.Point3>[...imagePositionPatient];
149
+ const topRight = <Types.Point3>[...imagePositionPatient];
150
+ const bottomLeft = <Types.Point3>[...imagePositionPatient];
151
+ const bottomRight = <Types.Point3>[...imagePositionPatient];
152
+
153
+ vec3.scaleAndAdd(
154
+ topRight,
155
+ imagePositionPatient,
156
+ columnCosines,
157
+ columns * columnPixelSpacing
158
+ );
159
+ vec3.scaleAndAdd(
160
+ bottomLeft,
161
+ imagePositionPatient,
162
+ rowCosines,
163
+ rows * rowPixelSpacing
164
+ );
165
+
166
+ vec3.scaleAndAdd(
167
+ bottomRight,
168
+ bottomLeft,
169
+ columnCosines,
170
+ columns * columnPixelSpacing
171
+ );
172
+
173
+ // check if the topLeft and bottomLeft line is parallel to the viewUp
174
+ const pointSet1 = [topLeft, bottomLeft, topRight, bottomRight];
175
+ const pointSet2 = [topLeft, topRight, bottomLeft, bottomRight];
176
+
177
+ return { pointSet1, pointSet2 };
178
+ };
179
+
180
+ /**
181
+ * it is used to draw the length annotation in each
182
+ * request animation frame. It calculates the updated cached statistics if
183
+ * data is invalidated and cache it.
184
+ *
185
+ * @param enabledElement - The Cornerstone's enabledElement.
186
+ * @param svgDrawingHelper - The svgDrawingHelper providing the context for drawing.
187
+ */
188
+ renderAnnotation = (
189
+ enabledElement: Types.IEnabledElement,
190
+ svgDrawingHelper: SVGDrawingHelper
191
+ ): boolean => {
192
+ const sourceImageIds = this.configuration.sourceImageIds;
193
+
194
+ let renderStatus = false;
195
+ if (!sourceImageIds?.length) {
196
+ return renderStatus;
197
+ }
198
+
199
+ const { viewport: targetViewport, FrameOfReferenceUID } = enabledElement;
200
+ const targetImageIds = targetViewport.getImageIds();
201
+ if (targetImageIds.length < 2) {
202
+ return renderStatus;
203
+ }
204
+
205
+ const annotations = getAnnotations(this.getToolName(), FrameOfReferenceUID);
206
+ if (!annotations?.length) {
207
+ return renderStatus;
208
+ }
209
+ const annotation = annotations[0];
210
+ const { annotationUID } = annotation;
211
+
212
+ const { focalPoint, viewPlaneNormal } = targetViewport.getCamera();
213
+
214
+ const styleSpecifier: StyleSpecifier = {
215
+ toolGroupId: this.toolGroupId,
216
+ toolName: this.getToolName(),
217
+ viewportId: enabledElement.viewport.id,
218
+ };
219
+ const imageIdNormal = <Types.Point3>(
220
+ this.getImageIdNormal(sourceImageIds[0])
221
+ );
222
+
223
+ if (this.isParallel(viewPlaneNormal, imageIdNormal)) {
224
+ // If the source and target viewports are parallel, we don't need to render
225
+ return renderStatus;
226
+ }
227
+
228
+ const targetViewportPlane = csUtils.planar.planeEquation(
229
+ viewPlaneNormal,
230
+ focalPoint
231
+ );
232
+
233
+ const pointSets = annotation.data.pointSets;
234
+ const viewportData = annotation.data.viewportData;
235
+ for (let i = 0; i < sourceImageIds.length; i++) {
236
+ // check if pointSets for the imageId was calculated. If not calculate and store
237
+ const { pointSet1, pointSet2 } = pointSets[i];
238
+
239
+ const targetData =
240
+ viewportData.get(targetViewport.id) ||
241
+ this.initializeViewportData(viewportData, targetViewport.id);
242
+
243
+ // check if pointSetToUse was calculated. If not calculate and store
244
+ if (!targetData.pointSetsToUse[i]) {
245
+ let pointSetToUse = pointSet1;
246
+
247
+ let topBottomVec = vec3.subtract(
248
+ vec3.create(),
249
+ pointSet1[0],
250
+ pointSet1[1]
251
+ );
252
+ topBottomVec = vec3.normalize(
253
+ vec3.create(),
254
+ topBottomVec
255
+ ) as Types.Point3;
256
+
257
+ // check if it is perpendicular to the viewPlaneNormal which means
258
+ // the line does not intersect the viewPlaneNormal
259
+ if (this.isPerpendicular(topBottomVec, viewPlaneNormal)) {
260
+ // 'use pointSet2';
261
+ pointSetToUse = pointSet2;
262
+ }
263
+
264
+ targetData.pointSetsToUse[i] = pointSetToUse;
265
+
266
+ targetData.lineStartsWorld[i] = csUtils.planar.linePlaneIntersection(
267
+ pointSetToUse[0],
268
+ pointSetToUse[1],
269
+ targetViewportPlane
270
+ );
271
+
272
+ targetData.lineEndsWorld[i] = csUtils.planar.linePlaneIntersection(
273
+ pointSetToUse[2],
274
+ pointSetToUse[3],
275
+ targetViewportPlane
276
+ );
277
+ }
278
+
279
+ const lineStartWorld = targetData.lineStartsWorld[i];
280
+ const lineEndWorld = targetData.lineEndsWorld[i];
281
+
282
+ styleSpecifier.annotationUID = annotationUID;
283
+ const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
284
+ const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
285
+ const color = this.getStyle('color', styleSpecifier, annotation);
286
+ const shadow = this.getStyle('shadow', styleSpecifier, annotation);
287
+
288
+ const canvasCoordinates = [lineStartWorld, lineEndWorld].map((world) =>
289
+ targetViewport.worldToCanvas(world)
290
+ );
291
+
292
+ const dataId = `${annotationUID}-line`;
293
+ const lineUID = `${i}`;
294
+ drawLineSvg(
295
+ svgDrawingHelper,
296
+ annotationUID,
297
+ lineUID,
298
+ canvasCoordinates[0],
299
+ canvasCoordinates[1],
300
+ {
301
+ color,
302
+ width: lineWidth,
303
+ lineDash,
304
+ shadow,
305
+ },
306
+ dataId
307
+ );
308
+ }
309
+
310
+ renderStatus = true;
311
+
312
+ return renderStatus;
313
+ };
314
+
315
+ private initializeViewportData = (viewportData, id) => {
316
+ viewportData.set(id, {
317
+ pointSetsToUse: [],
318
+ lineStartsWorld: [],
319
+ lineEndsWorld: [],
320
+ });
321
+
322
+ return viewportData.get(id);
323
+ };
324
+
325
+ private isPerpendicular = (
326
+ vec1: Types.Point3,
327
+ vec2: Types.Point3
328
+ ): boolean => {
329
+ const dot = vec3.dot(vec1, vec2);
330
+ return Math.abs(dot) < EPSILON;
331
+ };
332
+
333
+ private isParallel(vec1: Types.Point3, vec2: Types.Point3): boolean {
334
+ return Math.abs(vec3.dot(vec1, vec2)) > 1 - EPSILON;
335
+ }
336
+
337
+ private getImageIdNormal(imageId: string): vec3 {
338
+ const { imageOrientationPatient } = metaData.get(
339
+ 'imagePlaneModule',
340
+ imageId
341
+ );
342
+ const rowCosineVec = vec3.fromValues(
343
+ imageOrientationPatient[0],
344
+ imageOrientationPatient[1],
345
+ imageOrientationPatient[2]
346
+ );
347
+ const colCosineVec = vec3.fromValues(
348
+ imageOrientationPatient[3],
349
+ imageOrientationPatient[4],
350
+ imageOrientationPatient[5]
351
+ );
352
+ return vec3.cross(vec3.create(), rowCosineVec, colCosineVec);
353
+ }
354
+ }
355
+
356
+ OverlayGridTool.toolName = 'OverlayGrid';
357
+ export default OverlayGridTool;
@@ -11,6 +11,7 @@ import MIPJumpToClickTool from './MIPJumpToClickTool';
11
11
  import CrosshairsTool from './CrosshairsTool';
12
12
  import MagnifyTool from './MagnifyTool';
13
13
  import ReferenceLinesTool from './ReferenceLinesTool';
14
+ import OverlayGridTool from './OverlayGridTool';
14
15
  //
15
16
  import BidirectionalTool from './annotation/BidirectionalTool';
16
17
  import LengthTool from './annotation/LengthTool';
@@ -58,6 +59,7 @@ export {
58
59
  // Annotation Tools
59
60
  CrosshairsTool,
60
61
  ReferenceLinesTool,
62
+ OverlayGridTool,
61
63
  BidirectionalTool,
62
64
  LengthTool,
63
65
  ProbeTool,