@cornerstonejs/tools 4.17.4 → 4.18.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.
@@ -21,12 +21,20 @@ declare class CrosshairsTool extends AnnotationTool {
21
21
  _getReferenceLineControllable?: (viewportId: string) => boolean;
22
22
  _getReferenceLineDraggableRotatable?: (viewportId: string) => boolean;
23
23
  _getReferenceLineSlabThicknessControlsOn?: (viewportId: string) => boolean;
24
+ _volumeViewportNewVolumeListeners: Map<string, {
25
+ element: HTMLDivElement;
26
+ handler: EventListener;
27
+ }>;
28
+ _toolGroupViewportAddedListener: EventListener | null;
29
+ _toolGroupViewportRemovedListener: EventListener | null;
30
+ _ignoreFiredEvents: boolean;
24
31
  constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps);
25
32
  initializeViewport: ({ renderingEngineId, viewportId, }: Types.IViewportId) => {
26
33
  normal: Types.Point3;
27
34
  point: Types.Point3;
28
35
  };
29
36
  _getViewportsInfo: () => any[];
37
+ _reinitializeListenersAndCenter: () => void;
30
38
  onSetToolActive(): void;
31
39
  onSetToolPassive(): void;
32
40
  onSetToolEnabled(): void;
@@ -47,9 +55,9 @@ declare class CrosshairsTool extends AnnotationTool {
47
55
  filterInteractableAnnotationsForElement: (element: any, annotations: any) => any;
48
56
  renderAnnotation: (enabledElement: Types.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean;
49
57
  _getAnnotations: (enabledElement: Types.IEnabledElement) => Annotation[];
50
- _onNewVolume: () => void;
51
- _unsubscribeToViewportNewVolumeSet(viewportsInfo: any): void;
52
- _subscribeToViewportNewVolumeSet(viewports: any): void;
58
+ _onNewVolume: (_evt?: Event) => void;
59
+ _unsubscribeToViewportNewVolumeSet(_viewportsInfo: any): void;
60
+ _subscribeToViewportNewVolumeSet(_viewports: any): void;
53
61
  _autoPanViewportIfNecessary(viewportId: string, renderingEngine: Types.IRenderingEngine): void;
54
62
  _areViewportIdArraysEqual: (viewportIdArrayOne: any, viewportIdArrayTwo: any) => boolean;
55
63
  _getAnnotationsForViewportsWithDifferentCameras: (enabledElement: any, annotations: any) => any;
@@ -69,5 +77,16 @@ declare class CrosshairsTool extends AnnotationTool {
69
77
  _getRotationHandleNearImagePoint(viewport: any, annotation: any, canvasCoords: any, proximity: any): any;
70
78
  _getSlabThicknessHandleNearImagePoint(viewport: any, annotation: any, canvasCoords: any, proximity: any): any;
71
79
  _pointNearTool(element: any, annotation: any, canvasCoords: any, proximity: any): boolean;
80
+ _toViewportKey: (renderingEngineId: string, viewportId: string) => string;
81
+ _isFinitePoint3: (point: Types.Point3) => boolean;
82
+ _bindToolGroupViewportListeners: () => void;
83
+ _unbindToolGroupViewportListeners: () => void;
84
+ _syncVolumeListenersWithToolGroup: () => void;
85
+ _clearAllVolumeListenersAndViewportState: () => void;
86
+ _calculateToolCenterFromAbsoluteCameras: () => Types.Point3 | null;
87
+ _recomputeToolCenterFromAbsoluteCameras: ({ emitEvent, updateViewportCameras, }?: {
88
+ emitEvent?: boolean;
89
+ updateViewportCameras?: boolean;
90
+ }) => Types.Point3 | null;
72
91
  }
73
92
  export default CrosshairsTool;
@@ -69,6 +69,10 @@ class CrosshairsTool extends AnnotationTool {
69
69
  }) {
70
70
  super(toolProps, defaultToolProps);
71
71
  this.toolCenter = [0, 0, 0];
72
+ this._volumeViewportNewVolumeListeners = new Map();
73
+ this._toolGroupViewportAddedListener = null;
74
+ this._toolGroupViewportRemovedListener = null;
75
+ this._ignoreFiredEvents = false;
72
76
  this.initializeViewport = ({ renderingEngineId, viewportId, }) => {
73
77
  const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
74
78
  if (!enabledElement) {
@@ -114,11 +118,21 @@ class CrosshairsTool extends AnnotationTool {
114
118
  const viewports = getToolGroup(this.toolGroupId).viewportsInfo;
115
119
  return viewports;
116
120
  };
121
+ this._reinitializeListenersAndCenter = () => {
122
+ this._unbindToolGroupViewportListeners();
123
+ this._clearAllVolumeListenersAndViewportState();
124
+ this._bindToolGroupViewportListeners();
125
+ this._syncVolumeListenersWithToolGroup();
126
+ this._computeToolCenter(this._getViewportsInfo());
127
+ };
117
128
  this.resetCrosshairs = () => {
118
129
  const viewportsInfo = this._getViewportsInfo();
119
130
  for (const viewportInfo of viewportsInfo) {
120
131
  const { viewportId, renderingEngineId } = viewportInfo;
121
132
  const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
133
+ if (!enabledElement) {
134
+ continue;
135
+ }
122
136
  const viewport = enabledElement.viewport;
123
137
  const resetPan = true;
124
138
  const resetZoom = true;
@@ -152,31 +166,24 @@ class CrosshairsTool extends AnnotationTool {
152
166
  console.warn('For crosshairs to operate, at least two viewports must be given.');
153
167
  return;
154
168
  }
155
- const [firstViewport, secondViewport, thirdViewport] = viewportsInfo;
156
- const { normal: normal1, point: point1 } = this.initializeViewport(firstViewport);
157
- const { normal: normal2, point: point2 } = this.initializeViewport(secondViewport);
158
- let normal3 = [0, 0, 0];
159
- let point3 = vec3.create();
160
- if (thirdViewport) {
161
- ({ normal: normal3, point: point3 } =
162
- this.initializeViewport(thirdViewport));
163
- }
164
- else {
165
- vec3.add(point3, point1, point2);
166
- vec3.scale(point3, point3, 0.5);
167
- vec3.cross(normal3, normal1, normal2);
168
- }
169
- const firstPlane = csUtils.planar.planeEquation(normal1, point1);
170
- const secondPlane = csUtils.planar.planeEquation(normal2, point2);
171
- const thirdPlane = csUtils.planar.planeEquation(normal3, point3);
172
- const toolCenter = csUtils.planar.threePlaneIntersection(firstPlane, secondPlane, thirdPlane);
173
- this.setToolCenter(toolCenter);
169
+ viewportsInfo.forEach((viewportInfo) => {
170
+ this.initializeViewport(viewportInfo);
171
+ });
172
+ this._recomputeToolCenterFromAbsoluteCameras({
173
+ emitEvent: true,
174
+ updateViewportCameras: true,
175
+ });
174
176
  };
175
177
  this.addNewAnnotation = (evt) => {
176
178
  const eventDetail = evt.detail;
177
179
  const { element } = eventDetail;
178
180
  const { currentPoints } = eventDetail;
179
181
  const jumpWorld = currentPoints.world;
182
+ this._syncVolumeListenersWithToolGroup();
183
+ this._recomputeToolCenterFromAbsoluteCameras({
184
+ emitEvent: false,
185
+ updateViewportCameras: false,
186
+ });
180
187
  const enabledElement = getEnabledElement(element);
181
188
  const { viewport } = enabledElement;
182
189
  this._jump(enabledElement, jumpWorld);
@@ -231,46 +238,33 @@ class CrosshairsTool extends AnnotationTool {
231
238
  const eventDetail = evt.detail;
232
239
  const { element } = eventDetail;
233
240
  const enabledElement = getEnabledElement(element);
241
+ if (!enabledElement) {
242
+ return;
243
+ }
234
244
  const { renderingEngine } = enabledElement;
235
245
  const viewport = enabledElement.viewport;
246
+ this._syncVolumeListenersWithToolGroup();
247
+ if (this._ignoreFiredEvents) {
248
+ return;
249
+ }
250
+ const isSourceInToolGroup = this._getViewportsInfo().some(({ viewportId, renderingEngineId }) => viewportId === viewport.id && renderingEngineId === renderingEngine.id);
251
+ if (!isSourceInToolGroup) {
252
+ return;
253
+ }
236
254
  const annotations = this._getAnnotations(enabledElement);
237
255
  const filteredToolAnnotations = this.filterInteractableAnnotationsForElement(element, annotations);
238
256
  const viewportAnnotation = filteredToolAnnotations[0];
239
- if (!viewportAnnotation) {
240
- return;
241
- }
242
257
  const currentCamera = viewport.getCamera();
243
- const oldCameraPosition = viewportAnnotation.metadata.cameraPosition;
244
- const deltaCameraPosition = [0, 0, 0];
245
- vtkMath.subtract(currentCamera.position, oldCameraPosition, deltaCameraPosition);
246
- const oldCameraFocalPoint = viewportAnnotation.metadata.cameraFocalPoint;
247
- const deltaCameraFocalPoint = [0, 0, 0];
248
- vtkMath.subtract(currentCamera.focalPoint, oldCameraFocalPoint, deltaCameraFocalPoint);
249
- viewportAnnotation.metadata.cameraPosition = [...currentCamera.position];
250
- viewportAnnotation.metadata.cameraFocalPoint = [
251
- ...currentCamera.focalPoint,
252
- ];
253
- const viewportControllable = this._getReferenceLineControllable(viewport.id);
254
- const viewportDraggableRotatable = this._getReferenceLineDraggableRotatable(viewport.id);
255
- if (!csUtils.isEqual(currentCamera.position, oldCameraPosition, 1e-3) &&
256
- viewportControllable &&
257
- viewportDraggableRotatable) {
258
- let isRotation = false;
259
- const cameraModifiedSameForPosAndFocalPoint = csUtils.isEqual(deltaCameraPosition, deltaCameraFocalPoint, 1e-3);
260
- if (!cameraModifiedSameForPosAndFocalPoint) {
261
- isRotation = true;
262
- }
263
- const cameraModifiedInPlane = Math.abs(vtkMath.dot(deltaCameraPosition, currentCamera.viewPlaneNormal)) < 1e-2;
264
- if (!isRotation && !cameraModifiedInPlane) {
265
- this.toolCenter[0] += deltaCameraPosition[0];
266
- this.toolCenter[1] += deltaCameraPosition[1];
267
- this.toolCenter[2] += deltaCameraPosition[2];
268
- triggerEvent(eventTarget, Events.CROSSHAIR_TOOL_CENTER_CHANGED, {
269
- toolGroupId: this.toolGroupId,
270
- toolCenter: this.toolCenter,
271
- });
272
- }
258
+ if (viewportAnnotation) {
259
+ viewportAnnotation.metadata.cameraPosition = [...currentCamera.position];
260
+ viewportAnnotation.metadata.cameraFocalPoint = [
261
+ ...currentCamera.focalPoint,
262
+ ];
273
263
  }
264
+ this._recomputeToolCenterFromAbsoluteCameras({
265
+ emitEvent: true,
266
+ updateViewportCameras: false,
267
+ });
274
268
  if (this.configuration.autoPan?.enabled) {
275
269
  const toolGroup = getToolGroupForViewport(viewport.id, renderingEngine.id);
276
270
  const otherViewportIds = toolGroup
@@ -708,15 +702,19 @@ class CrosshairsTool extends AnnotationTool {
708
702
  });
709
703
  return toolGroupAnnotations;
710
704
  };
711
- this._onNewVolume = () => {
712
- const viewportsInfo = this._getViewportsInfo();
713
- this._computeToolCenter(viewportsInfo);
705
+ this._onNewVolume = (_evt) => {
706
+ this._syncVolumeListenersWithToolGroup();
707
+ this._recomputeToolCenterFromAbsoluteCameras({
708
+ emitEvent: true,
709
+ updateViewportCameras: false,
710
+ });
714
711
  };
715
712
  this._areViewportIdArraysEqual = (viewportIdArrayOne, viewportIdArrayTwo) => {
716
713
  if (viewportIdArrayOne.length !== viewportIdArrayTwo.length) {
717
714
  return false;
718
715
  }
719
- viewportIdArrayOne.forEach((id) => {
716
+ for (let index = 0; index < viewportIdArrayOne.length; index++) {
717
+ const id = viewportIdArrayOne[index];
720
718
  let itemFound = false;
721
719
  for (let i = 0; i < viewportIdArrayTwo.length; ++i) {
722
720
  if (id === viewportIdArrayTwo[i]) {
@@ -727,7 +725,7 @@ class CrosshairsTool extends AnnotationTool {
727
725
  if (itemFound === false) {
728
726
  return false;
729
727
  }
730
- });
728
+ }
731
729
  return true;
732
730
  };
733
731
  this._getAnnotationsForViewportsWithDifferentCameras = (enabledElement, annotations) => {
@@ -903,10 +901,19 @@ class CrosshairsTool extends AnnotationTool {
903
901
  return false;
904
902
  }
905
903
  this._applyDeltaShiftToSelectedViewportCameras(renderingEngine, viewportsAnnotationsToUpdate, delta);
904
+ this._recomputeToolCenterFromAbsoluteCameras({
905
+ emitEvent: true,
906
+ updateViewportCameras: false,
907
+ });
906
908
  state.isInteractingWithTool = false;
907
909
  return true;
908
910
  };
909
911
  this._activateModify = (element) => {
912
+ this._syncVolumeListenersWithToolGroup();
913
+ this._recomputeToolCenterFromAbsoluteCameras({
914
+ emitEvent: false,
915
+ updateViewportCameras: false,
916
+ });
910
917
  state.isInteractingWithTool = !this.configuration.mobile?.enabled;
911
918
  element.addEventListener(Events.MOUSE_UP, this._endCallback);
912
919
  element.addEventListener(Events.MOUSE_DRAG, this._dragCallback);
@@ -927,9 +934,15 @@ class CrosshairsTool extends AnnotationTool {
927
934
  this._endCallback = (evt) => {
928
935
  const eventDetail = evt.detail;
929
936
  const { element } = eventDetail;
930
- this.editData.annotation.data.handles.activeOperation = null;
931
- this.editData.annotation.data.activeViewportIds = [];
937
+ if (this.editData?.annotation?.data) {
938
+ this.editData.annotation.data.handles.activeOperation = null;
939
+ this.editData.annotation.data.activeViewportIds = [];
940
+ }
932
941
  this._deactivateModify(element);
942
+ this._recomputeToolCenterFromAbsoluteCameras({
943
+ emitEvent: true,
944
+ updateViewportCameras: false,
945
+ });
933
946
  resetElementCursor(element);
934
947
  this.editData = null;
935
948
  const requireSameOrientation = false;
@@ -968,6 +981,10 @@ class CrosshairsTool extends AnnotationTool {
968
981
  viewportAnnotation.data.activeViewportIds.find((id) => id === otherViewport.id));
969
982
  });
970
983
  this._applyDeltaShiftToSelectedViewportCameras(renderingEngine, viewportsAnnotationsToUpdate, delta);
984
+ this._recomputeToolCenterFromAbsoluteCameras({
985
+ emitEvent: true,
986
+ updateViewportCameras: false,
987
+ });
971
988
  }
972
989
  else if (handles.activeOperation === OPERATION.ROTATE) {
973
990
  const otherViewportAnnotations = this._getAnnotationsForViewportsWithDifferentCameras(enabledElement, annotations);
@@ -1004,29 +1021,40 @@ class CrosshairsTool extends AnnotationTool {
1004
1021
  .rotate(angle, rotationAxis)
1005
1022
  .translate(-center[0], -center[1], -center[2]);
1006
1023
  const otherViewportsIds = [];
1007
- viewportsAnnotationsToUpdate.forEach((annotation) => {
1008
- const { data } = annotation;
1009
- data.handles.toolCenter = center;
1010
- const otherViewport = renderingEngine.getViewport(data.viewportId);
1011
- const camera = otherViewport.getCamera();
1012
- const { viewUp, position, focalPoint } = camera;
1013
- viewUp[0] += position[0];
1014
- viewUp[1] += position[1];
1015
- viewUp[2] += position[2];
1016
- vec3.transformMat4(focalPoint, focalPoint, matrix);
1017
- vec3.transformMat4(position, position, matrix);
1018
- vec3.transformMat4(viewUp, viewUp, matrix);
1019
- viewUp[0] -= position[0];
1020
- viewUp[1] -= position[1];
1021
- viewUp[2] -= position[2];
1022
- otherViewport.setCamera({
1023
- position,
1024
- viewUp,
1025
- focalPoint,
1024
+ const previousIgnoreFiredEvents = this._ignoreFiredEvents;
1025
+ this._ignoreFiredEvents = true;
1026
+ try {
1027
+ viewportsAnnotationsToUpdate.forEach((annotation) => {
1028
+ const { data } = annotation;
1029
+ data.handles.toolCenter = center;
1030
+ const otherViewport = renderingEngine.getViewport(data.viewportId);
1031
+ const camera = otherViewport.getCamera();
1032
+ const { viewUp, position, focalPoint } = camera;
1033
+ viewUp[0] += position[0];
1034
+ viewUp[1] += position[1];
1035
+ viewUp[2] += position[2];
1036
+ vec3.transformMat4(focalPoint, focalPoint, matrix);
1037
+ vec3.transformMat4(position, position, matrix);
1038
+ vec3.transformMat4(viewUp, viewUp, matrix);
1039
+ viewUp[0] -= position[0];
1040
+ viewUp[1] -= position[1];
1041
+ viewUp[2] -= position[2];
1042
+ otherViewport.setCamera({
1043
+ position,
1044
+ viewUp,
1045
+ focalPoint,
1046
+ });
1047
+ otherViewportsIds.push(otherViewport.id);
1026
1048
  });
1027
- otherViewportsIds.push(otherViewport.id);
1028
- });
1049
+ }
1050
+ finally {
1051
+ this._ignoreFiredEvents = previousIgnoreFiredEvents;
1052
+ }
1029
1053
  renderingEngine.renderViewports(otherViewportsIds);
1054
+ this._recomputeToolCenterFromAbsoluteCameras({
1055
+ emitEvent: true,
1056
+ updateViewportCameras: false,
1057
+ });
1030
1058
  }
1031
1059
  else if (handles.activeOperation === OPERATION.SLAB) {
1032
1060
  const otherViewportAnnotations = this._getAnnotationsForViewportsWithDifferentCameras(enabledElement, annotations);
@@ -1113,7 +1141,14 @@ class CrosshairsTool extends AnnotationTool {
1113
1141
  }
1114
1142
  });
1115
1143
  renderingEngine.renderViewports(viewportsIds);
1144
+ this._recomputeToolCenterFromAbsoluteCameras({
1145
+ emitEvent: true,
1146
+ updateViewportCameras: false,
1147
+ });
1116
1148
  }
1149
+ const requireSameOrientation = false;
1150
+ const viewportIdsToRender = getViewportIdsWithToolToRender(element, this.getToolName(), requireSameOrientation);
1151
+ triggerAnnotationRenderForViewportIds(viewportIdsToRender);
1117
1152
  };
1118
1153
  this._pointNearReferenceLine = (annotation, canvasCoords, proximity, lineViewport) => {
1119
1154
  const { data } = annotation;
@@ -1156,6 +1191,170 @@ class CrosshairsTool extends AnnotationTool {
1156
1191
  }
1157
1192
  return false;
1158
1193
  };
1194
+ this._toViewportKey = (renderingEngineId, viewportId) => {
1195
+ return `${renderingEngineId}::${viewportId}`;
1196
+ };
1197
+ this._isFinitePoint3 = (point) => {
1198
+ if (!point || point.length !== 3) {
1199
+ return false;
1200
+ }
1201
+ return (Number.isFinite(point[0]) &&
1202
+ Number.isFinite(point[1]) &&
1203
+ Number.isFinite(point[2]));
1204
+ };
1205
+ this._bindToolGroupViewportListeners = () => {
1206
+ if (!this._toolGroupViewportAddedListener) {
1207
+ this._toolGroupViewportAddedListener = ((evt) => {
1208
+ if (evt.detail?.toolGroupId !== this.toolGroupId) {
1209
+ return;
1210
+ }
1211
+ this._syncVolumeListenersWithToolGroup();
1212
+ this._computeToolCenter(this._getViewportsInfo());
1213
+ });
1214
+ eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._toolGroupViewportAddedListener);
1215
+ }
1216
+ if (!this._toolGroupViewportRemovedListener) {
1217
+ this._toolGroupViewportRemovedListener = ((evt) => {
1218
+ if (evt.detail?.toolGroupId !== this.toolGroupId) {
1219
+ return;
1220
+ }
1221
+ this._syncVolumeListenersWithToolGroup();
1222
+ this._recomputeToolCenterFromAbsoluteCameras({
1223
+ emitEvent: true,
1224
+ updateViewportCameras: false,
1225
+ });
1226
+ });
1227
+ eventTarget.addEventListener(Events.TOOLGROUP_VIEWPORT_REMOVED, this._toolGroupViewportRemovedListener);
1228
+ }
1229
+ };
1230
+ this._unbindToolGroupViewportListeners = () => {
1231
+ if (this._toolGroupViewportAddedListener) {
1232
+ eventTarget.removeEventListener(Events.TOOLGROUP_VIEWPORT_ADDED, this._toolGroupViewportAddedListener);
1233
+ this._toolGroupViewportAddedListener = null;
1234
+ }
1235
+ if (this._toolGroupViewportRemovedListener) {
1236
+ eventTarget.removeEventListener(Events.TOOLGROUP_VIEWPORT_REMOVED, this._toolGroupViewportRemovedListener);
1237
+ this._toolGroupViewportRemovedListener = null;
1238
+ }
1239
+ };
1240
+ this._syncVolumeListenersWithToolGroup = () => {
1241
+ const viewportsInfo = this._getViewportsInfo();
1242
+ const activeViewportKeys = new Set();
1243
+ viewportsInfo.forEach((viewportInfo) => {
1244
+ const { viewportId, renderingEngineId } = viewportInfo;
1245
+ const viewportKey = this._toViewportKey(renderingEngineId, viewportId);
1246
+ activeViewportKeys.add(viewportKey);
1247
+ const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
1248
+ const existingListenerInfo = this._volumeViewportNewVolumeListeners.get(viewportKey);
1249
+ if (!enabledElement) {
1250
+ if (existingListenerInfo) {
1251
+ existingListenerInfo.element.removeEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, existingListenerInfo.handler);
1252
+ this._volumeViewportNewVolumeListeners.delete(viewportKey);
1253
+ }
1254
+ return;
1255
+ }
1256
+ const { viewport } = enabledElement;
1257
+ const { element } = viewport;
1258
+ if (existingListenerInfo && existingListenerInfo.element !== element) {
1259
+ existingListenerInfo.element.removeEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, existingListenerInfo.handler);
1260
+ this._volumeViewportNewVolumeListeners.delete(viewportKey);
1261
+ }
1262
+ if (this._volumeViewportNewVolumeListeners.has(viewportKey)) {
1263
+ return;
1264
+ }
1265
+ const handler = ((evt) => this._onNewVolume(evt));
1266
+ element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, handler);
1267
+ this._volumeViewportNewVolumeListeners.set(viewportKey, {
1268
+ element,
1269
+ handler,
1270
+ });
1271
+ });
1272
+ Array.from(this._volumeViewportNewVolumeListeners.entries()).forEach(([viewportKey, listenerInfo]) => {
1273
+ if (activeViewportKeys.has(viewportKey)) {
1274
+ return;
1275
+ }
1276
+ listenerInfo.element.removeEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, listenerInfo.handler);
1277
+ this._volumeViewportNewVolumeListeners.delete(viewportKey);
1278
+ });
1279
+ };
1280
+ this._clearAllVolumeListenersAndViewportState = () => {
1281
+ this._volumeViewportNewVolumeListeners.forEach((listenerInfo) => {
1282
+ listenerInfo.element.removeEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, listenerInfo.handler);
1283
+ });
1284
+ this._volumeViewportNewVolumeListeners.clear();
1285
+ };
1286
+ this._calculateToolCenterFromAbsoluteCameras = () => {
1287
+ const viewportsInfo = this._getViewportsInfo();
1288
+ const uniquePlanes = [];
1289
+ viewportsInfo.forEach((viewportInfo) => {
1290
+ const enabledElement = getEnabledElementByIds(viewportInfo.viewportId, viewportInfo.renderingEngineId);
1291
+ if (!enabledElement) {
1292
+ return;
1293
+ }
1294
+ const camera = enabledElement.viewport.getCamera();
1295
+ const normal = [...camera.viewPlaneNormal];
1296
+ const point = [...camera.focalPoint];
1297
+ if (!this._isFinitePoint3(normal) || !this._isFinitePoint3(point)) {
1298
+ return;
1299
+ }
1300
+ vec3.normalize(normal, normal);
1301
+ const alreadyTracked = uniquePlanes.some((plane) => csUtils.isEqual(plane.normal, normal, 1e-3) ||
1302
+ csUtils.isOpposite(plane.normal, normal, 1e-3));
1303
+ if (!alreadyTracked) {
1304
+ uniquePlanes.push({ normal, point });
1305
+ }
1306
+ });
1307
+ if (uniquePlanes.length < 2) {
1308
+ return null;
1309
+ }
1310
+ const firstPlane = csUtils.planar.planeEquation(uniquePlanes[0].normal, uniquePlanes[0].point);
1311
+ const secondPlane = csUtils.planar.planeEquation(uniquePlanes[1].normal, uniquePlanes[1].point);
1312
+ let thirdPlane;
1313
+ if (uniquePlanes.length >= 3) {
1314
+ thirdPlane = csUtils.planar.planeEquation(uniquePlanes[2].normal, uniquePlanes[2].point);
1315
+ }
1316
+ else {
1317
+ const thirdNormal = vec3.create();
1318
+ vec3.cross(thirdNormal, uniquePlanes[0].normal, uniquePlanes[1].normal);
1319
+ if (vec3.length(thirdNormal) < 1e-6) {
1320
+ return null;
1321
+ }
1322
+ vec3.normalize(thirdNormal, thirdNormal);
1323
+ const thirdPoint = this._isFinitePoint3(this.toolCenter)
1324
+ ? [...this.toolCenter]
1325
+ : [
1326
+ (uniquePlanes[0].point[0] + uniquePlanes[1].point[0]) * 0.5,
1327
+ (uniquePlanes[0].point[1] + uniquePlanes[1].point[1]) * 0.5,
1328
+ (uniquePlanes[0].point[2] + uniquePlanes[1].point[2]) * 0.5,
1329
+ ];
1330
+ thirdPlane = csUtils.planar.planeEquation(thirdNormal, thirdPoint);
1331
+ }
1332
+ const center = csUtils.planar.threePlaneIntersection(firstPlane, secondPlane, thirdPlane);
1333
+ return this._isFinitePoint3(center) ? center : null;
1334
+ };
1335
+ this._recomputeToolCenterFromAbsoluteCameras = ({ emitEvent = true, updateViewportCameras = false, } = {}) => {
1336
+ const toolCenter = this._calculateToolCenterFromAbsoluteCameras();
1337
+ if (!toolCenter) {
1338
+ return null;
1339
+ }
1340
+ const hasChanged = !csUtils.isEqual(this.toolCenter, toolCenter, 1e-3);
1341
+ if (!hasChanged) {
1342
+ return toolCenter;
1343
+ }
1344
+ if (updateViewportCameras) {
1345
+ this.setToolCenter(toolCenter, !emitEvent);
1346
+ }
1347
+ else {
1348
+ this.toolCenter = toolCenter;
1349
+ if (emitEvent) {
1350
+ triggerEvent(eventTarget, Events.CROSSHAIR_TOOL_CENTER_CHANGED, {
1351
+ toolGroupId: this.toolGroupId,
1352
+ toolCenter: this.toolCenter,
1353
+ });
1354
+ }
1355
+ }
1356
+ return toolCenter;
1357
+ };
1159
1358
  this._getReferenceLineColor =
1160
1359
  toolProps.configuration?.getReferenceLineColor ||
1161
1360
  defaultReferenceLineColor;
@@ -1170,22 +1369,21 @@ class CrosshairsTool extends AnnotationTool {
1170
1369
  defaultReferenceLineSlabThicknessControlsOn;
1171
1370
  }
1172
1371
  onSetToolActive() {
1173
- const viewportsInfo = this._getViewportsInfo();
1174
- this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
1175
- this._subscribeToViewportNewVolumeSet(viewportsInfo);
1176
- this._computeToolCenter(viewportsInfo);
1372
+ this._reinitializeListenersAndCenter();
1177
1373
  }
1178
1374
  onSetToolPassive() {
1179
- const viewportsInfo = this._getViewportsInfo();
1180
- this._computeToolCenter(viewportsInfo);
1375
+ this._reinitializeListenersAndCenter();
1181
1376
  }
1182
1377
  onSetToolEnabled() {
1183
- const viewportsInfo = this._getViewportsInfo();
1184
- this._computeToolCenter(viewportsInfo);
1378
+ this._reinitializeListenersAndCenter();
1185
1379
  }
1186
1380
  onSetToolDisabled() {
1187
1381
  const viewportsInfo = this._getViewportsInfo();
1188
- this._unsubscribeToViewportNewVolumeSet(viewportsInfo);
1382
+ this._unbindToolGroupViewportListeners();
1383
+ this._clearAllVolumeListenersAndViewportState();
1384
+ this._ignoreFiredEvents = false;
1385
+ this.editData = null;
1386
+ state.isInteractingWithTool = false;
1189
1387
  viewportsInfo.forEach(({ renderingEngineId, viewportId }) => {
1190
1388
  const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
1191
1389
  if (!enabledElement) {
@@ -1201,40 +1399,53 @@ class CrosshairsTool extends AnnotationTool {
1201
1399
  }
1202
1400
  setToolCenter(toolCenter, suppressEvents = false) {
1203
1401
  const viewportsInfo = this._getViewportsInfo();
1204
- viewportsInfo.map(({ renderingEngineId, viewportId }) => {
1205
- const renderingEngine = getRenderingEngine(renderingEngineId);
1206
- const viewport = renderingEngine.getViewport(viewportId);
1207
- const camera = viewport.getCamera();
1208
- const { focalPoint, position, viewPlaneNormal } = camera;
1209
- const delta = [
1210
- toolCenter[0] - focalPoint[0],
1211
- toolCenter[1] - focalPoint[1],
1212
- toolCenter[2] - focalPoint[2],
1213
- ];
1214
- const scroll = delta[0] * viewPlaneNormal[0] +
1215
- delta[1] * viewPlaneNormal[1] +
1216
- delta[2] * viewPlaneNormal[2];
1217
- const scrollDelta = [
1218
- scroll * viewPlaneNormal[0],
1219
- scroll * viewPlaneNormal[1],
1220
- scroll * viewPlaneNormal[2],
1221
- ];
1222
- const newFocalPoint = [
1223
- focalPoint[0] + scrollDelta[0],
1224
- focalPoint[1] + scrollDelta[1],
1225
- focalPoint[2] + scrollDelta[2],
1226
- ];
1227
- const newPosition = [
1228
- position[0] + scrollDelta[0],
1229
- position[1] + scrollDelta[1],
1230
- position[2] + scrollDelta[2],
1231
- ];
1232
- viewport.setCamera({
1233
- focalPoint: newFocalPoint,
1234
- position: newPosition,
1402
+ const previousIgnoreFiredEvents = this._ignoreFiredEvents;
1403
+ this._ignoreFiredEvents = true;
1404
+ try {
1405
+ viewportsInfo.forEach(({ renderingEngineId, viewportId }) => {
1406
+ const renderingEngine = getRenderingEngine(renderingEngineId);
1407
+ if (!renderingEngine) {
1408
+ return;
1409
+ }
1410
+ const viewport = renderingEngine.getViewport(viewportId);
1411
+ if (!viewport) {
1412
+ return;
1413
+ }
1414
+ const camera = viewport.getCamera();
1415
+ const { focalPoint, position, viewPlaneNormal } = camera;
1416
+ const delta = [
1417
+ toolCenter[0] - focalPoint[0],
1418
+ toolCenter[1] - focalPoint[1],
1419
+ toolCenter[2] - focalPoint[2],
1420
+ ];
1421
+ const scroll = delta[0] * viewPlaneNormal[0] +
1422
+ delta[1] * viewPlaneNormal[1] +
1423
+ delta[2] * viewPlaneNormal[2];
1424
+ const scrollDelta = [
1425
+ scroll * viewPlaneNormal[0],
1426
+ scroll * viewPlaneNormal[1],
1427
+ scroll * viewPlaneNormal[2],
1428
+ ];
1429
+ const newFocalPoint = [
1430
+ focalPoint[0] + scrollDelta[0],
1431
+ focalPoint[1] + scrollDelta[1],
1432
+ focalPoint[2] + scrollDelta[2],
1433
+ ];
1434
+ const newPosition = [
1435
+ position[0] + scrollDelta[0],
1436
+ position[1] + scrollDelta[1],
1437
+ position[2] + scrollDelta[2],
1438
+ ];
1439
+ viewport.setCamera({
1440
+ focalPoint: newFocalPoint,
1441
+ position: newPosition,
1442
+ });
1443
+ viewport.render();
1235
1444
  });
1236
- viewport.render();
1237
- });
1445
+ }
1446
+ finally {
1447
+ this._ignoreFiredEvents = previousIgnoreFiredEvents;
1448
+ }
1238
1449
  this.toolCenter = toolCenter;
1239
1450
  if (!suppressEvents) {
1240
1451
  triggerEvent(eventTarget, Events.CROSSHAIR_TOOL_CENTER_CHANGED, {
@@ -1255,19 +1466,11 @@ class CrosshairsTool extends AnnotationTool {
1255
1466
  return point;
1256
1467
  }
1257
1468
  }
1258
- _unsubscribeToViewportNewVolumeSet(viewportsInfo) {
1259
- viewportsInfo.forEach(({ viewportId, renderingEngineId }) => {
1260
- const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
1261
- const { element } = viewport;
1262
- element.removeEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1263
- });
1469
+ _unsubscribeToViewportNewVolumeSet(_viewportsInfo) {
1470
+ this._syncVolumeListenersWithToolGroup();
1264
1471
  }
1265
- _subscribeToViewportNewVolumeSet(viewports) {
1266
- viewports.forEach(({ viewportId, renderingEngineId }) => {
1267
- const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId);
1268
- const { element } = viewport;
1269
- element.addEventListener(Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, this._onNewVolume);
1270
- });
1472
+ _subscribeToViewportNewVolumeSet(_viewports) {
1473
+ this._syncVolumeListenersWithToolGroup();
1271
1474
  }
1272
1475
  _autoPanViewportIfNecessary(viewportId, renderingEngine) {
1273
1476
  const viewport = renderingEngine.getViewport(viewportId);
@@ -1312,10 +1515,17 @@ class CrosshairsTool extends AnnotationTool {
1312
1515
  focalPoint[1] - deltaPointsWorld[1],
1313
1516
  focalPoint[2] - deltaPointsWorld[2],
1314
1517
  ];
1315
- viewport.setCamera({
1316
- focalPoint: updatedFocalPoint,
1317
- position: updatedPosition,
1318
- });
1518
+ const previousIgnoreFiredEvents = this._ignoreFiredEvents;
1519
+ this._ignoreFiredEvents = true;
1520
+ try {
1521
+ viewport.setCamera({
1522
+ focalPoint: updatedFocalPoint,
1523
+ position: updatedPosition,
1524
+ });
1525
+ }
1526
+ finally {
1527
+ this._ignoreFiredEvents = previousIgnoreFiredEvents;
1528
+ }
1319
1529
  viewport.render();
1320
1530
  }
1321
1531
  setSlabThickness(viewport, slabThickness) {
@@ -1356,10 +1566,17 @@ class CrosshairsTool extends AnnotationTool {
1356
1566
  const newPosition = [0, 0, 0];
1357
1567
  vtkMath.add(camera.focalPoint, projectedDelta, newFocalPoint);
1358
1568
  vtkMath.add(camera.position, projectedDelta, newPosition);
1359
- viewport.setCamera({
1360
- focalPoint: newFocalPoint,
1361
- position: newPosition,
1362
- });
1569
+ const previousIgnoreFiredEvents = this._ignoreFiredEvents;
1570
+ this._ignoreFiredEvents = true;
1571
+ try {
1572
+ viewport.setCamera({
1573
+ focalPoint: newFocalPoint,
1574
+ position: newPosition,
1575
+ });
1576
+ }
1577
+ finally {
1578
+ this._ignoreFiredEvents = previousIgnoreFiredEvents;
1579
+ }
1363
1580
  viewport.render();
1364
1581
  }
1365
1582
  }
@@ -298,31 +298,40 @@ class ProbeTool extends AnnotationTool {
298
298
  continue;
299
299
  }
300
300
  const { dimensions, imageData, metadata, voxelManager } = image;
301
- const modality = metadata.Modality;
301
+ const modality = metadata?.Modality;
302
302
  let ijk = transformWorldToIndex(imageData, worldPos);
303
303
  ijk = vec3.round(ijk, ijk);
304
304
  if (csUtils.indexWithinDimensions(ijk, dimensions)) {
305
305
  this.isHandleOutsideImage = false;
306
- let value = voxelManager.getAtIJKPoint(ijk);
307
- if (targetId.startsWith('imageId:')) {
306
+ if (targetId.startsWith('imageId:') && modality !== 'ECG') {
308
307
  const imageId = targetId.split('imageId:')[1];
309
308
  const imageURI = csUtils.imageIdToURI(imageId);
310
309
  const viewports = csUtils.getViewportsWithImageURI(imageURI);
311
310
  const viewport = viewports[0];
312
311
  ijk[2] = viewport.getCurrentImageIdIndex();
313
312
  }
313
+ let value;
314
314
  let modalityUnit;
315
- if (modality === 'US') {
315
+ if (modality === 'ECG') {
316
316
  const calibratedResults = getCalibratedProbeUnitsAndValue(image, [
317
317
  ijk,
318
318
  ]);
319
- const hasEnhancedRegionValues = calibratedResults.values.every((value) => value !== null);
319
+ value = calibratedResults.values;
320
+ modalityUnit = calibratedResults.units;
321
+ }
322
+ else if (modality === 'US') {
323
+ value = voxelManager?.getAtIJKPoint(ijk);
324
+ const calibratedResults = getCalibratedProbeUnitsAndValue(image, [
325
+ ijk,
326
+ ]);
327
+ const hasEnhancedRegionValues = calibratedResults.values.every((v) => v !== null);
320
328
  value = (hasEnhancedRegionValues ? calibratedResults.values : value);
321
329
  modalityUnit = hasEnhancedRegionValues
322
330
  ? calibratedResults.units
323
331
  : 'raw';
324
332
  }
325
333
  else {
334
+ value = voxelManager?.getAtIJKPoint(ijk);
326
335
  modalityUnit = getPixelValueUnits(modality, annotation.metadata.referencedImageId, pixelUnitsOptions);
327
336
  }
328
337
  cachedStats[targetId] = {
@@ -1,5 +1,5 @@
1
1
  import { ChangeTypes, Events } from '../../enums';
2
- import { getEnabledElement, utilities as csUtils, StackViewport, } from '@cornerstonejs/core';
2
+ import { getEnabledElement, utilities as csUtils, StackViewport, ECGViewport, } from '@cornerstonejs/core';
3
3
  import { AnnotationTool } from '../base';
4
4
  import throttle from '../../utilities/throttle';
5
5
  import { addAnnotation, getAnnotations, removeAnnotation, } from '../../stateManagement/annotation/annotationState';
@@ -35,8 +35,9 @@ class UltrasoundDirectionalTool extends AnnotationTool {
35
35
  const worldPos = currentPoints.world;
36
36
  const enabledElement = getEnabledElement(element);
37
37
  const { viewport } = enabledElement;
38
- if (!(viewport instanceof StackViewport)) {
39
- throw new Error('UltrasoundDirectionalTool can only be used on a StackViewport');
38
+ if (!(viewport instanceof StackViewport) &&
39
+ !(viewport instanceof ECGViewport)) {
40
+ throw new Error('UltrasoundDirectionalTool can only be used on a StackViewport or ECGViewport');
40
41
  }
41
42
  hideElementCursor(element);
42
43
  this.isDrawing = true;
@@ -11,6 +11,7 @@ const SUPPORTED_REGION_DATA_TYPES = [
11
11
  const SUPPORTED_PROBE_VARIANT = [
12
12
  '4,3',
13
13
  '4,7',
14
+ '4,-1',
14
15
  ];
15
16
  const UNIT_MAPPING = {
16
17
  0: 'px',
@@ -24,6 +25,7 @@ const UNIT_MAPPING = {
24
25
  8: 'cm\xb2',
25
26
  9: 'cm\xb2/s',
26
27
  0xc: 'degrees',
28
+ [-1]: 'mV',
27
29
  };
28
30
  const EPS = 1e-3;
29
31
  const SQUARE = '\xb2';
@@ -66,7 +68,9 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
66
68
  const region = calibration.sequenceOfUltrasoundRegions.find((region) => handles.every((handle) => handle[0] >= region.regionLocationMinX0 &&
67
69
  handle[0] <= region.regionLocationMaxX1 &&
68
70
  handle[1] >= region.regionLocationMinY0 &&
69
- handle[1] <= region.regionLocationMaxY1) && SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType));
71
+ handle[1] <= region.regionLocationMaxY1) &&
72
+ (SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) ||
73
+ SUPPORTED_PROBE_VARIANT.includes(`${region.physicalUnitsXDirection},${region.physicalUnitsYDirection}`)));
70
74
  if (region &&
71
75
  region.physicalUnitsXDirection === region.physicalUnitsYDirection) {
72
76
  const physicalDeltaX = Math.abs(region.physicalDeltaX);
@@ -77,6 +81,19 @@ const getCalibratedLengthUnitsAndScale = (image, handles) => {
77
81
  unit = UNIT_MAPPING[region.physicalUnitsXDirection] || 'unknown';
78
82
  areaUnit = unit + SQUARE;
79
83
  }
84
+ else if (region && region.physicalUnitsYDirection === -1) {
85
+ const physicalDeltaX = Math.abs(region.physicalDeltaX);
86
+ const physicalDeltaY = Math.abs(region.physicalDeltaY);
87
+ scale = 1 / physicalDeltaX;
88
+ scaleY = 1 / physicalDeltaY;
89
+ calibrationType = 'ECG Region';
90
+ unit =
91
+ UNIT_MAPPING[region.physicalUnitsXDirection] ||
92
+ UNIT_MAPPING[region.physicalUnitsYDirection] ||
93
+ 'unknown';
94
+ areaUnit =
95
+ (UNIT_MAPPING[region.physicalUnitsYDirection] || 'px') + SQUARE;
96
+ }
80
97
  }
81
98
  else if (calibration.scale) {
82
99
  scale = calibration.scale;
@@ -101,7 +118,8 @@ const getCalibratedProbeUnitsAndValue = (image, handles) => {
101
118
  return { units, values };
102
119
  }
103
120
  if (calibration.sequenceOfUltrasoundRegions) {
104
- const supportedRegionsMetadata = calibration.sequenceOfUltrasoundRegions.filter((region) => SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) &&
121
+ const supportedRegionsMetadata = calibration.sequenceOfUltrasoundRegions.filter((region) => (SUPPORTED_REGION_DATA_TYPES.includes(region.regionDataType) ||
122
+ SUPPORTED_PROBE_VARIANT.includes(`${region.physicalUnitsXDirection},${region.physicalUnitsYDirection}`)) &&
105
123
  SUPPORTED_PROBE_VARIANT.includes(`${region.physicalUnitsXDirection},${region.physicalUnitsYDirection}`));
106
124
  if (!supportedRegionsMetadata?.length) {
107
125
  return { units, values };
@@ -119,11 +137,12 @@ const getCalibratedProbeUnitsAndValue = (image, handles) => {
119
137
  physicalDeltaY;
120
138
  const xValue = (imageIndex[0] - region.regionLocationMinX0 - referencePixelX0) *
121
139
  physicalDeltaX;
122
- calibrationType = 'US Region';
140
+ calibrationType =
141
+ region.physicalUnitsYDirection === -1 ? 'ECG Region' : 'US Region';
123
142
  values = [xValue, yValue];
124
143
  units = [
125
- UNIT_MAPPING[region.physicalUnitsXDirection],
126
- UNIT_MAPPING[region.physicalUnitsYDirection],
144
+ UNIT_MAPPING[region.physicalUnitsXDirection] ?? 'unknown',
145
+ UNIT_MAPPING[region.physicalUnitsYDirection] ?? 'unknown',
127
146
  ];
128
147
  }
129
148
  return {
@@ -1 +1 @@
1
- export declare const version = "4.17.4";
1
+ export declare const version = "4.18.0";
@@ -1 +1 @@
1
- export const version = '4.17.4';
1
+ export const version = '4.18.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/tools",
3
- "version": "4.17.4",
3
+ "version": "4.18.0",
4
4
  "description": "Cornerstone3D Tools",
5
5
  "types": "./dist/esm/index.d.ts",
6
6
  "module": "./dist/esm/index.js",
@@ -108,7 +108,7 @@
108
108
  "canvas": "3.2.0"
109
109
  },
110
110
  "peerDependencies": {
111
- "@cornerstonejs/core": "4.17.4",
111
+ "@cornerstonejs/core": "4.18.0",
112
112
  "@kitware/vtk.js": "34.15.1",
113
113
  "@types/d3-array": "3.2.1",
114
114
  "@types/d3-interpolate": "3.0.4",
@@ -127,5 +127,5 @@
127
127
  "type": "individual",
128
128
  "url": "https://ohif.org/donate"
129
129
  },
130
- "gitHead": "3664bf4622c54d790aa3684f42967448a1bddea3"
130
+ "gitHead": "7f4a99858ac39083fb1aeab650002ec7d33e3d58"
131
131
  }