@cornerstonejs/tools 4.0.0-beta.2 → 4.0.0-beta.4

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/dist/esm/enums/Events.d.ts +2 -0
  3. package/dist/esm/enums/Events.js +2 -0
  4. package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js +5 -1
  5. package/dist/esm/index.d.ts +2 -2
  6. package/dist/esm/index.js +2 -2
  7. package/dist/esm/stateManagement/annotation/annotationVisibility.js +2 -0
  8. package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.d.ts +1 -0
  9. package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.js +11 -0
  10. package/dist/esm/stateManagement/segmentation/config/segmentationVisibility.js +3 -0
  11. package/dist/esm/stateManagement/segmentation/helpers/normalizeSegmentationInput.js +13 -0
  12. package/dist/esm/stateManagement/segmentation/internalAddSegmentationRepresentation.js +11 -0
  13. package/dist/esm/store/ToolGroupManager/ToolGroup.js +10 -11
  14. package/dist/esm/tools/CrosshairsTool.d.ts +13 -12
  15. package/dist/esm/tools/CrosshairsTool.js +10 -3
  16. package/dist/esm/tools/OrientationMarkerTool.js +4 -2
  17. package/dist/esm/tools/OverlayGridTool.d.ts +2 -2
  18. package/dist/esm/tools/SegmentationIntersectionTool.d.ts +8 -5
  19. package/dist/esm/tools/SegmentationIntersectionTool.js +1 -1
  20. package/dist/esm/tools/VolumeCroppingControlTool.d.ts +91 -0
  21. package/dist/esm/tools/VolumeCroppingControlTool.js +1208 -0
  22. package/dist/esm/tools/VolumeCroppingTool.d.ts +96 -0
  23. package/dist/esm/tools/VolumeCroppingTool.js +1065 -0
  24. package/dist/esm/tools/WindowLevelTool.js +1 -1
  25. package/dist/esm/tools/annotation/ProbeTool.js +1 -0
  26. package/dist/esm/tools/annotation/UltrasoundPleuraBLineTool/utils/generateConvexHullFromContour.js +3 -3
  27. package/dist/esm/tools/annotation/VideoRedactionTool.d.ts +1 -1
  28. package/dist/esm/tools/annotation/VideoRedactionTool.js +1 -1
  29. package/dist/esm/tools/base/AnnotationTool.d.ts +1 -11
  30. package/dist/esm/tools/base/BaseTool.d.ts +2 -0
  31. package/dist/esm/tools/base/BaseTool.js +6 -0
  32. package/dist/esm/tools/index.d.ts +3 -1
  33. package/dist/esm/tools/index.js +3 -1
  34. package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +30 -16
  35. package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +2 -0
  36. package/dist/esm/tools/segmentation/LabelmapBaseTool.js +11 -0
  37. package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +9 -3
  38. package/dist/esm/tools/segmentation/SegmentLabelTool.js +17 -5
  39. package/dist/esm/tools/segmentation/strategies/BrushStrategy.d.ts +1 -0
  40. package/dist/esm/tools/segmentation/strategies/compositions/preview.js +2 -0
  41. package/dist/esm/tools/segmentation/strategies/fillCircle.d.ts +3 -6
  42. package/dist/esm/tools/segmentation/strategies/fillCircle.js +53 -30
  43. package/dist/esm/tools/segmentation/strategies/fillRectangle.js +26 -24
  44. package/dist/esm/tools/segmentation/strategies/fillSphere.js +14 -12
  45. package/dist/esm/types/AnnotationTypes.d.ts +22 -19
  46. package/dist/esm/types/ContourAnnotation.d.ts +1 -0
  47. package/dist/esm/types/index.d.ts +2 -2
  48. package/dist/esm/utilities/planar/filterAnnotationsWithinSlice.js +17 -5
  49. package/dist/esm/utilities/segmentation/getBrushToolInstances.js +2 -2
  50. package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.d.ts +1 -1
  51. package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.js +20 -10
  52. package/dist/esm/utilities/stackPrefetch/stackPrefetch.js +12 -2
  53. package/dist/esm/version.d.ts +1 -1
  54. package/dist/esm/version.js +1 -1
  55. package/package.json +5 -4
@@ -143,7 +143,7 @@ class WindowLevelTool extends BaseTool {
143
143
  }, [Infinity, -Infinity]);
144
144
  const BitsStored = imageVolume?.metadata?.BitsStored;
145
145
  const metadataDynamicRange = BitsStored ? 2 ** BitsStored : Infinity;
146
- imageDynamicRange = Math.min(calculatedDynamicRange, metadataDynamicRange);
146
+ imageDynamicRange = Math.min(calculatedDynamicRange[1] - calculatedDynamicRange[0], metadataDynamicRange);
147
147
  }
148
148
  else {
149
149
  imageDynamicRange = this._getImageDynamicRangeFromViewport(viewport);
@@ -333,6 +333,7 @@ class ProbeTool extends AnnotationTool {
333
333
  Modality: modality,
334
334
  modalityUnit,
335
335
  };
336
+ annotation.invalidated = true;
336
337
  }
337
338
  else {
338
339
  this.isHandleOutsideImage = true;
@@ -1,6 +1,6 @@
1
- import { utilities } from '@cornerstonejs/tools';
1
+ import * as math from '../../../../utilities/math';
2
2
  export function generateConvexHullFromContour(contour) {
3
- const simplified = utilities.math.polyline.decimate(contour, 2);
4
- const hull = utilities.math.polyline.convexHull(simplified);
3
+ const simplified = math.polyline.decimate(contour, 2);
4
+ const hull = math.polyline.convexHull(simplified);
5
5
  return { simplified, hull };
6
6
  }
@@ -22,7 +22,7 @@ declare class VideoRedactionTool extends AnnotationTool {
22
22
  handleSelectedCallback: (evt: any, annotation: any, handle: any, interactionType?: string) => void;
23
23
  _endCallback: (evt: any) => void;
24
24
  _dragCallback: (evt: any) => void;
25
- cancel(element: any): any;
25
+ cancel(element: any): string;
26
26
  _activateDraw: (element: any) => void;
27
27
  _deactivateDraw: (element: any) => void;
28
28
  _activateModify: (element: any) => void;
@@ -424,7 +424,7 @@ class VideoRedactionTool extends AnnotationTool {
424
424
  data.handles.activeHandleIndex = null;
425
425
  triggerAnnotationRenderForViewportIds(viewportUIDsToRender);
426
426
  this.editData = null;
427
- return annotation.metadata.annotationUID;
427
+ return annotation.annotationUID;
428
428
  }
429
429
  _getImageVolumeFromTargetUID(targetUID, renderingEngine) {
430
430
  let imageVolume, viewport;
@@ -40,17 +40,7 @@ declare abstract class AnnotationTool extends AnnotationDisplayTool {
40
40
  private _imagePointNearToolOrHandle;
41
41
  protected static createAnnotationState(annotation: Annotation, deleting?: boolean): {
42
42
  annotationUID: string;
43
- data: {
44
- [key: string]: unknown;
45
- handles?: import("../../types/AnnotationTypes").Handles;
46
- cachedStats?: Record<string, unknown>;
47
- label?: string;
48
- contour?: {
49
- polyline?: Types.Point3[];
50
- pointsManager?: Types.IPointsManager<Types.Point3>;
51
- closed?: boolean;
52
- };
53
- };
43
+ data: import("../../types").AnnotationData;
54
44
  deleting: boolean;
55
45
  };
56
46
  static createAnnotationMemo(element: any, annotation: Annotation, options?: {
@@ -34,5 +34,7 @@ declare abstract class BaseTool {
34
34
  restoreMemo: () => void;
35
35
  };
36
36
  doneEditMemo(): void;
37
+ static startGroupRecording(): void;
38
+ static endGroupRecording(): void;
37
39
  }
38
40
  export default BaseTool;
@@ -123,6 +123,12 @@ class BaseTool {
123
123
  }
124
124
  this.memo = null;
125
125
  }
126
+ static startGroupRecording() {
127
+ DefaultHistoryMemo.startGroupRecording();
128
+ }
129
+ static endGroupRecording() {
130
+ DefaultHistoryMemo.endGroupRecording();
131
+ }
126
132
  }
127
133
  BaseTool.toolName = 'BaseTool';
128
134
  export default BaseTool;
@@ -1,6 +1,8 @@
1
1
  import { BaseTool, AnnotationTool, AnnotationDisplayTool } from './base';
2
2
  import PanTool from './PanTool';
3
3
  import TrackballRotateTool from './TrackballRotateTool';
4
+ import VolumeCroppingTool from './VolumeCroppingTool';
5
+ import VolumeCroppingControlTool from './VolumeCroppingControlTool';
4
6
  import WindowLevelTool from './WindowLevelTool';
5
7
  import WindowLevelRegionTool from './WindowLevelRegionTool';
6
8
  import StackScrollTool from './StackScrollTool';
@@ -58,4 +60,4 @@ import SegmentBidirectionalTool from './segmentation/SegmentBidirectionalTool';
58
60
  import * as strategies from './segmentation/strategies';
59
61
  import SegmentLabelTool from './segmentation/SegmentLabelTool';
60
62
  import LabelMapEditWithContourTool from './segmentation/LabelmapEditWithContour';
61
- export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
63
+ export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, VolumeCroppingTool, VolumeCroppingControlTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
@@ -1,6 +1,8 @@
1
1
  import { BaseTool, AnnotationTool, AnnotationDisplayTool } from './base';
2
2
  import PanTool from './PanTool';
3
3
  import TrackballRotateTool from './TrackballRotateTool';
4
+ import VolumeCroppingTool from './VolumeCroppingTool';
5
+ import VolumeCroppingControlTool from './VolumeCroppingControlTool';
4
6
  import WindowLevelTool from './WindowLevelTool';
5
7
  import WindowLevelRegionTool from './WindowLevelRegionTool';
6
8
  import StackScrollTool from './StackScrollTool';
@@ -58,4 +60,4 @@ import SegmentBidirectionalTool from './segmentation/SegmentBidirectionalTool';
58
60
  import * as strategies from './segmentation/strategies';
59
61
  import SegmentLabelTool from './segmentation/SegmentLabelTool';
60
62
  import LabelMapEditWithContourTool from './segmentation/LabelmapEditWithContour';
61
- export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
63
+ export { BaseTool, AnnotationTool, AnnotationDisplayTool, PanTool, TrackballRotateTool, VolumeCroppingTool, VolumeCroppingControlTool, DragProbeTool, WindowLevelTool, WindowLevelRegionTool, StackScrollTool, PlanarRotateTool, ZoomTool, MIPJumpToClickTool, ReferenceCursors, CrosshairsTool, ReferenceLinesTool, OverlayGridTool, SegmentationIntersectionTool, BidirectionalTool, LabelTool, LengthTool, HeightTool, ProbeTool, RectangleROITool, EllipticalROITool, CircleROITool, ETDRSGridTool, SplineROITool, PlanarFreehandROITool, PlanarFreehandContourSegmentationTool, LivewireContourTool, LivewireContourSegmentationTool, ArrowAnnotateTool, AngleTool, CobbAngleTool, UltrasoundDirectionalTool, UltrasoundPleuraBLineTool, KeyImageTool, AnnotationEraserTool as EraserTool, RectangleScissorsTool, CircleScissorsTool, SphereScissorsTool, RectangleROIThresholdTool, RectangleROIStartEndThresholdTool, CircleROIStartEndThresholdTool, SplineContourSegmentationTool, BrushTool, MagnifyTool, AdvancedMagnifyTool, PaintFillTool, ScaleOverlayTool, OrientationMarkerTool, SculptorTool, SegmentSelectTool, VolumeRotateTool, RegionSegmentTool, RegionSegmentPlusTool, WholeBodySegmentTool, LabelmapBaseTool, SegmentBidirectionalTool, SegmentLabelTool, LabelMapEditWithContourTool, strategies, };
@@ -140,7 +140,8 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
140
140
  this._deactivateModify(element);
141
141
  this._deactivateDraw(element);
142
142
  resetElementCursor(element);
143
- const enabledElement = getEnabledElement(element);
143
+ const { metadata } = annotation;
144
+ const { enabledElement } = metadata;
144
145
  this.editData = null;
145
146
  this.isDrawing = false;
146
147
  if (this.isHandleOutsideImage &&
@@ -176,6 +177,7 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
176
177
  const { annotationUID, data, metadata } = annotation;
177
178
  const { startCoordinate, endCoordinate } = data;
178
179
  const { points, activeHandleIndex } = data.handles;
180
+ const { enabledElement: annotationEnabledElement } = metadata;
179
181
  styleSpecifier.annotationUID = annotationUID;
180
182
  const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
181
183
  const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
@@ -219,8 +221,12 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
219
221
  isMiddleSlice = true;
220
222
  }
221
223
  data.handles.points[0][this._getIndexOfCoordinatesForViewplaneNormal(viewplaneNormal)] = middleCoordinate;
222
- if (annotation.invalidated) {
223
- this._throttledCalculateCachedStats(annotation, enabledElement);
224
+ const iteratorVolumeIDs = annotationEnabledElement.viewport?.volumeIds.values();
225
+ for (const volumeId of iteratorVolumeIDs) {
226
+ if (annotation.invalidated &&
227
+ annotation.metadata.volumeId === volumeId) {
228
+ this._throttledCalculateCachedStats(annotation, annotationEnabledElement);
229
+ }
224
230
  }
225
231
  if (!viewport.getRenderingEngine()) {
226
232
  console.warn('Rendering Engine has been destroyed');
@@ -323,24 +329,32 @@ class CircleROIStartEndThresholdTool extends CircleROITool {
323
329
  const { viewPlaneNormal, spacingInNormal } = metadata;
324
330
  const { startCoordinate, endCoordinate } = data;
325
331
  const { points } = data.handles;
326
- const handlesToStart = csUtils.deepClone(points);
332
+ const projectionAxisIndex = this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal);
327
333
  const startWorld = vec3.clone(points[0]);
334
+ startWorld[projectionAxisIndex] = startCoordinate;
328
335
  const endWorld = vec3.clone(points[0]);
329
- const indexOfNormal = this._getIndexOfCoordinatesForViewplaneNormal(viewPlaneNormal);
330
- startWorld[indexOfNormal] = startCoordinate;
331
- endWorld[indexOfNormal] = endCoordinate;
332
- handlesToStart.forEach((handlePoint) => {
333
- handlePoint[indexOfNormal] = startCoordinate;
334
- });
335
- const distance = vec3.distance(startWorld, endWorld);
336
- const newProjectionPoints = [];
337
- if (distance >= 0) {
338
- newProjectionPoints.push(handlesToStart.map((p) => Array.from(p)));
336
+ endWorld[projectionAxisIndex] = endCoordinate;
337
+ const direction = vec3.create();
338
+ vec3.subtract(direction, endWorld, startWorld);
339
+ const distance = vec3.length(direction);
340
+ if (distance === 0) {
341
+ const handlesOnStartPlane = points.map((p) => {
342
+ const newPoint = vec3.clone(p);
343
+ newPoint[projectionAxisIndex] = startCoordinate;
344
+ return Array.from(newPoint);
345
+ });
346
+ data.cachedStats.projectionPoints = [handlesOnStartPlane];
347
+ return;
339
348
  }
340
- for (let dist = spacingInNormal; dist <= distance; dist += spacingInNormal) {
349
+ vec3.normalize(direction, direction);
350
+ const handlesToStart = csUtils.deepClone(points);
351
+ handlesToStart[0][projectionAxisIndex] = startCoordinate;
352
+ handlesToStart[1][projectionAxisIndex] = startCoordinate;
353
+ const newProjectionPoints = [];
354
+ for (let dist = 0; dist <= distance + 1e-6; dist += spacingInNormal) {
341
355
  newProjectionPoints.push(handlesToStart.map((point) => {
342
356
  const newPoint = vec3.create();
343
- vec3.scaleAndAdd(newPoint, point, viewPlaneNormal, dist);
357
+ vec3.scaleAndAdd(newPoint, point, direction, dist);
344
358
  return Array.from(newPoint);
345
359
  }));
346
360
  }
@@ -68,6 +68,8 @@ export default class LabelmapBaseTool extends BaseTool {
68
68
  constructor(toolProps: any, defaultToolProps: any);
69
69
  protected _historyRedoHandler(evt: any): void;
70
70
  protected get _previewData(): PreviewData;
71
+ hasPreviewData(): boolean;
72
+ shouldResolvePreviewRequests(): boolean;
71
73
  createMemo(segmentationId: string, segmentationVoxelManager: any): LabelmapMemo.LabelmapMemo;
72
74
  protected createEditData(element: any): EditDataReturnType;
73
75
  protected getEditData({ viewport, representationData, segmentsLocked, segmentationId, }: {
@@ -54,6 +54,13 @@ export default class LabelmapBaseTool extends BaseTool {
54
54
  get _previewData() {
55
55
  return LabelmapBaseTool.previewData;
56
56
  }
57
+ hasPreviewData() {
58
+ return !!this._previewData.preview;
59
+ }
60
+ shouldResolvePreviewRequests() {
61
+ return ((this.mode === 'Active' || this.mode === 'Enabled') &&
62
+ this.hasPreviewData());
63
+ }
57
64
  createMemo(segmentationId, segmentationVoxelManager) {
58
65
  const voxelManagerId = segmentationVoxelManager.id;
59
66
  if (this.memo &&
@@ -229,6 +236,10 @@ export default class LabelmapBaseTool extends BaseTool {
229
236
  const enabledElement = getEnabledElement(element);
230
237
  const results = this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.AddPreview);
231
238
  _previewData.isDrag = true;
239
+ if (results?.modified) {
240
+ _previewData.preview = results;
241
+ _previewData.element = element;
242
+ }
232
243
  return results;
233
244
  }
234
245
  rejectPreview(element = this._previewData.element) {
@@ -128,7 +128,8 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
128
128
  this._deactivateModify(element);
129
129
  this._deactivateDraw(element);
130
130
  resetElementCursor(element);
131
- const enabledElement = getEnabledElement(element);
131
+ const { metadata } = annotation;
132
+ const { enabledElement } = metadata;
132
133
  this.editData = null;
133
134
  this.isDrawing = false;
134
135
  if (this.isHandleOutsideImage &&
@@ -164,6 +165,7 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
164
165
  const { annotationUID, data, metadata } = annotation;
165
166
  const { startCoordinate, endCoordinate } = data;
166
167
  const { points, activeHandleIndex } = data.handles;
168
+ const { enabledElement: annotationEnabledElement } = metadata;
167
169
  const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
168
170
  styleSpecifier.annotationUID = annotationUID;
169
171
  const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
@@ -194,8 +196,12 @@ class RectangleROIStartEndThresholdTool extends RectangleROITool {
194
196
  roundedCoord > Math.max(roundedStartCoord, roundedEndCoord)) {
195
197
  continue;
196
198
  }
197
- if (annotation.invalidated) {
198
- this._throttledCalculateCachedStats(annotation, enabledElement);
199
+ const iteratorVolumeIDs = annotationEnabledElement.viewport?.volumeIds.values();
200
+ for (const volumeId of iteratorVolumeIDs) {
201
+ if (annotation.invalidated &&
202
+ annotation.metadata.volumeId === volumeId) {
203
+ this._throttledCalculateCachedStats(annotation, annotationEnabledElement);
204
+ }
199
205
  }
200
206
  let firstOrLastSlice = false;
201
207
  if (roundedCoord === roundedStartCoord ||
@@ -1,11 +1,12 @@
1
1
  import { getEnabledElement } from '@cornerstonejs/core';
2
+ import { config as segmentationConfig } from '../../stateManagement/segmentation';
2
3
  import { BaseTool } from '../base';
3
4
  import { triggerSegmentationModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
4
5
  import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds';
5
6
  import { getActiveSegmentation } from '../../stateManagement/segmentation/activeSegmentation';
6
7
  import { getSegmentIndexAtWorldPoint } from '../../utilities/segmentation';
7
8
  import { state } from '../../store/state';
8
- import { drawLinkedTextBox as drawLinkedTextBoxSvg } from '../../drawingSvg';
9
+ import { drawTextBox as drawTextBoxSvg } from '../../drawingSvg';
9
10
  class SegmentLabelTool extends BaseTool {
10
11
  constructor(toolProps = {
11
12
  data: {
@@ -26,6 +27,8 @@ class SegmentLabelTool extends BaseTool {
26
27
  configuration: {
27
28
  hoverTimeout: 100,
28
29
  searchRadius: 6,
30
+ color: null,
31
+ background: null,
29
32
  },
30
33
  }) {
31
34
  super(toolProps, defaultToolProps);
@@ -90,13 +93,15 @@ class SegmentLabelTool extends BaseTool {
90
93
  viewport,
91
94
  });
92
95
  const segment = activeSegmentation.segments[hoveredSegmentIndex];
96
+ const color = this.configuration.color ??
97
+ segmentationConfig.color.getSegmentIndexColor(viewport.id, segmentationId, hoveredSegmentIndex);
93
98
  const label = segment?.label;
94
99
  const canvasCoordinates = viewport.worldToCanvas(worldPoint);
95
100
  this._editData = {
96
101
  hoveredSegmentIndex,
97
102
  hoveredSegmentLabel: label,
98
103
  canvasCoordinates,
99
- worldPoint,
104
+ color,
100
105
  };
101
106
  if (!hoveredSegmentIndex || hoveredSegmentIndex === 0) {
102
107
  return;
@@ -111,12 +116,19 @@ class SegmentLabelTool extends BaseTool {
111
116
  return;
112
117
  }
113
118
  const { viewport } = enabledElement;
114
- const { hoveredSegmentIndex, hoveredSegmentLabel, canvasCoordinates, worldPoint, } = this._editData;
119
+ const { hoveredSegmentIndex, hoveredSegmentLabel, canvasCoordinates, color, } = this._editData;
115
120
  if (!hoveredSegmentIndex) {
116
121
  return;
117
122
  }
118
- const textBoxPosition = viewport.worldToCanvas(worldPoint);
119
- const boundingBox = drawLinkedTextBoxSvg(svgDrawingHelper, 'segmentSelectLabelAnnotation', 'segmentSelectLabelTextBox', [hoveredSegmentLabel ? hoveredSegmentLabel : '(unnamed segment)'], textBoxPosition, [canvasCoordinates], {}, {});
123
+ const offset = -15;
124
+ const textBoxPosition = [
125
+ canvasCoordinates[0] + offset,
126
+ canvasCoordinates[1] + offset,
127
+ ];
128
+ const boundingBox = drawTextBoxSvg(svgDrawingHelper, 'segmentSelectLabelAnnotation', 'segmentSelectLabelTextBox', [hoveredSegmentLabel ?? '(unnamed segment)'], textBoxPosition, {
129
+ color: `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`,
130
+ background: this.configuration.background ?? undefined,
131
+ });
120
132
  const left = canvasCoordinates[0];
121
133
  const top = canvasCoordinates[1];
122
134
  const { width, height } = boundingBox;
@@ -37,6 +37,7 @@ export type InitializedOperationData = LabelmapToolOperationDataAny & {
37
37
  };
38
38
  };
39
39
  memo?: LabelmapMemo;
40
+ modified?: boolean;
40
41
  };
41
42
  export type StrategyFunction = (operationData: InitializedOperationData, ...args: any[]) => unknown;
42
43
  export type CompositionInstance = {
@@ -18,6 +18,7 @@ export default {
18
18
  },
19
19
  [StrategyCallbacks.Initialize]: (operationData) => {
20
20
  const { segmentIndex, previewColor, previewSegmentIndex } = operationData;
21
+ operationData.modified = false;
21
22
  if (previewSegmentIndex == null || segmentIndex == null) {
22
23
  return;
23
24
  }
@@ -25,6 +26,7 @@ export default {
25
26
  viewportIds?.forEach((viewportId) => {
26
27
  setSegmentIndexColor(viewportId, operationData.segmentationId, previewSegmentIndex, previewColor);
27
28
  });
29
+ operationData.modified = true;
28
30
  },
29
31
  [StrategyCallbacks.AcceptPreview]: (operationData) => {
30
32
  const { previewSegmentIndex, segmentationVoxelManager, memo, segmentIndex, centerSegmentIndexInfo, } = operationData || {};
@@ -1,11 +1,8 @@
1
- import { vec3 } from 'gl-matrix';
2
1
  import type { Types } from '@cornerstonejs/core';
3
2
  import BrushStrategy from './BrushStrategy';
4
- declare function createPointInEllipse(worldInfo: {
5
- topLeftWorld: Types.Point3;
6
- bottomRightWorld: Types.Point3;
7
- center: Types.Point3 | vec3;
8
- }): (pointLPS: Types.Point3) => boolean;
3
+ import type { CanvasCoordinates } from '../../../types';
4
+ export declare function getEllipseCornersFromCanvasCoordinates(canvasCoordinates: CanvasCoordinates): Array<Types.Point2>;
5
+ declare function createPointInEllipse(cornersInWorld?: Types.Point3[]): (pointLPS: Types.Point3) => boolean;
9
6
  declare const CIRCLE_STRATEGY: BrushStrategy;
10
7
  declare const CIRCLE_THRESHOLD_STRATEGY: BrushStrategy;
11
8
  declare const fillInsideCircle: (enabledElement: any, operationData: any) => unknown;
@@ -1,50 +1,67 @@
1
1
  import { vec3 } from 'gl-matrix';
2
2
  import { utilities as csUtils } from '@cornerstonejs/core';
3
- import { getCanvasEllipseCorners, precalculatePointInEllipse, } from '../../../utilities/math/ellipse';
4
3
  import { getBoundingBoxAroundShapeIJK } from '../../../utilities/boundingBox';
5
4
  import BrushStrategy from './BrushStrategy';
6
5
  import { StrategyCallbacks } from '../../../enums';
7
6
  import compositions from './compositions';
8
7
  import { pointInSphere } from '../../../utilities/math/sphere';
9
8
  const { transformWorldToIndex, isEqual } = csUtils;
9
+ export function getEllipseCornersFromCanvasCoordinates(canvasCoordinates) {
10
+ const [bottom, top, left, right] = canvasCoordinates;
11
+ const topLeft = [left[0], top[1]];
12
+ const bottomRight = [right[0], bottom[1]];
13
+ const bottomLeft = [left[0], bottom[1]];
14
+ const topRight = [right[0], top[1]];
15
+ return [topLeft, bottomRight, bottomLeft, topRight];
16
+ }
10
17
  const initializeCircle = {
11
18
  [StrategyCallbacks.Initialize]: (operationData) => {
12
19
  const { points, viewport, segmentationImageData, } = operationData;
13
20
  if (!points) {
14
21
  return;
15
22
  }
16
- const center = vec3.fromValues(0, 0, 0);
17
- points.forEach((point) => {
18
- vec3.add(center, center, point);
19
- });
20
- vec3.scale(center, center, 1 / points.length);
23
+ const center = vec3.create();
24
+ if (points.length >= 2) {
25
+ vec3.add(center, points[0], points[1]);
26
+ vec3.scale(center, center, 0.5);
27
+ }
28
+ else {
29
+ vec3.copy(center, points[0]);
30
+ }
21
31
  operationData.centerWorld = center;
22
32
  operationData.centerIJK = transformWorldToIndex(segmentationImageData, center);
23
33
  const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
24
- const [topLeftCanvas, bottomRightCanvas] = getCanvasEllipseCorners(canvasCoordinates);
25
- const topLeftWorld = viewport.canvasToWorld(topLeftCanvas);
26
- const bottomRightWorld = viewport.canvasToWorld(bottomRightCanvas);
34
+ const corners = getEllipseCornersFromCanvasCoordinates(canvasCoordinates);
35
+ const cornersInWorld = corners.map((corner) => viewport.canvasToWorld(corner));
27
36
  const circleCornersIJK = points.map((world) => {
28
37
  return transformWorldToIndex(segmentationImageData, world);
29
38
  });
30
39
  const boundsIJK = getBoundingBoxAroundShapeIJK(circleCornersIJK, segmentationImageData.getDimensions());
31
- operationData.isInObject = createPointInEllipse({
32
- topLeftWorld,
33
- bottomRightWorld,
34
- center,
35
- });
40
+ operationData.isInObject = createPointInEllipse(cornersInWorld);
36
41
  operationData.isInObjectBoundsIJK = boundsIJK;
37
42
  },
38
43
  };
39
- function createPointInEllipse(worldInfo) {
40
- const { topLeftWorld, bottomRightWorld, center } = worldInfo;
41
- const xRadius = Math.abs(topLeftWorld[0] - bottomRightWorld[0]) / 2;
42
- const yRadius = Math.abs(topLeftWorld[1] - bottomRightWorld[1]) / 2;
43
- const zRadius = Math.abs(topLeftWorld[2] - bottomRightWorld[2]) / 2;
44
- const radius = Math.max(xRadius, yRadius, zRadius);
45
- if (isEqual(xRadius, radius) &&
46
- isEqual(yRadius, radius) &&
47
- isEqual(zRadius, radius)) {
44
+ function createPointInEllipse(cornersInWorld = []) {
45
+ if (!cornersInWorld || cornersInWorld.length !== 4) {
46
+ throw new Error('createPointInEllipse: cornersInWorld must have 4 points');
47
+ }
48
+ const [topLeft, bottomRight, bottomLeft, topRight] = cornersInWorld;
49
+ const center = vec3.create();
50
+ vec3.add(center, topLeft, bottomRight);
51
+ vec3.scale(center, center, 0.5);
52
+ const majorAxisVec = vec3.create();
53
+ vec3.subtract(majorAxisVec, topRight, topLeft);
54
+ const xRadius = vec3.length(majorAxisVec) / 2;
55
+ vec3.normalize(majorAxisVec, majorAxisVec);
56
+ const minorAxisVec = vec3.create();
57
+ vec3.subtract(minorAxisVec, bottomLeft, topLeft);
58
+ const yRadius = vec3.length(minorAxisVec) / 2;
59
+ vec3.normalize(minorAxisVec, minorAxisVec);
60
+ const normal = vec3.create();
61
+ vec3.cross(normal, majorAxisVec, minorAxisVec);
62
+ vec3.normalize(normal, normal);
63
+ if (isEqual(xRadius, yRadius)) {
64
+ const radius = xRadius;
48
65
  const sphereObj = {
49
66
  center,
50
67
  radius,
@@ -52,14 +69,20 @@ function createPointInEllipse(worldInfo) {
52
69
  };
53
70
  return (pointLPS) => pointInSphere(sphereObj, pointLPS);
54
71
  }
55
- const ellipseObj = {
56
- center: center,
57
- xRadius,
58
- yRadius,
59
- zRadius,
72
+ return (pointLPS) => {
73
+ const pointVec = vec3.create();
74
+ vec3.subtract(pointVec, pointLPS, center);
75
+ const distToPlane = vec3.dot(pointVec, normal);
76
+ const proj = vec3.create();
77
+ vec3.scaleAndAdd(proj, pointVec, normal, -distToPlane);
78
+ const fromTopLeft = vec3.create();
79
+ const centerToTopLeft = vec3.create();
80
+ vec3.subtract(centerToTopLeft, center, topLeft);
81
+ vec3.subtract(fromTopLeft, proj, centerToTopLeft);
82
+ const x = vec3.dot(fromTopLeft, majorAxisVec);
83
+ const y = vec3.dot(fromTopLeft, minorAxisVec);
84
+ return (x * x) / (xRadius * xRadius) + (y * y) / (yRadius * yRadius) <= 1;
60
85
  };
61
- const { precalculated } = precalculatePointInEllipse(ellipseObj, {});
62
- return precalculated;
63
86
  }
64
87
  const CIRCLE_STRATEGY = new BrushStrategy('Circle', compositions.regionFill, compositions.setValue, initializeCircle, compositions.determineSegmentIndex, compositions.preview, compositions.labelmapStatistics);
65
88
  const CIRCLE_THRESHOLD_STRATEGY = new BrushStrategy('CircleThreshold', compositions.regionFill, compositions.setValue, initializeCircle, compositions.determineSegmentIndex, compositions.dynamicThreshold, compositions.threshold, compositions.preview, compositions.islandRemoval, compositions.labelmapStatistics);
@@ -1,16 +1,13 @@
1
1
  import { vec3 } from 'gl-matrix';
2
2
  import { utilities as csUtils, StackViewport } from '@cornerstonejs/core';
3
- import { getBoundingBoxAroundShapeIJK, getBoundingBoxAroundShapeWorld, } from '../../../utilities/boundingBox';
4
- import { triggerSegmentationDataModified } from '../../../stateManagement/segmentation/triggerSegmentationEvents';
5
- import { getStrategyData } from './utils/getStrategyData';
6
- import { isAxisAlignedRectangle } from '../../../utilities/rectangleROITool/isAxisAlignedRectangle';
3
+ import { getBoundingBoxAroundShapeIJK } from '../../../utilities/boundingBox';
7
4
  import BrushStrategy from './BrushStrategy';
8
5
  import { StrategyCallbacks } from '../../../enums';
9
6
  import compositions from './compositions';
10
7
  const { transformWorldToIndex } = csUtils;
11
8
  const initializeRectangle = {
12
9
  [StrategyCallbacks.Initialize]: (operationData) => {
13
- const { points, imageVoxelManager, viewport, segmentationImageData, segmentationVoxelManager, } = operationData;
10
+ const { points, viewport, segmentationImageData, } = operationData;
14
11
  if (!points) {
15
12
  return;
16
13
  }
@@ -36,8 +33,18 @@ function createPointInRectangle(viewport, points, segmentationImageData) {
36
33
  });
37
34
  });
38
35
  const boundsIJK = getBoundingBoxAroundShapeIJK(rectangleCornersIJK, segmentationImageData.getDimensions());
39
- const isStackViewport = viewport instanceof StackViewport;
40
- const isAligned = isStackViewport || isAxisAlignedRectangle(rectangleCornersIJK);
36
+ const [p0, p1, p2, p3] = points;
37
+ const axisU = vec3.create();
38
+ const axisV = vec3.create();
39
+ vec3.subtract(axisU, p1, p0);
40
+ vec3.subtract(axisV, p3, p0);
41
+ const uLen = vec3.length(axisU);
42
+ const vLen = vec3.length(axisV);
43
+ vec3.normalize(axisU, axisU);
44
+ vec3.normalize(axisV, axisV);
45
+ const normal = vec3.create();
46
+ vec3.cross(normal, axisU, axisV);
47
+ vec3.normalize(normal, normal);
41
48
  const direction = segmentationImageData.getDirection();
42
49
  const spacing = segmentationImageData.getSpacing();
43
50
  const { viewPlaneNormal } = viewport.getCamera();
@@ -45,23 +52,18 @@ function createPointInRectangle(viewport, points, segmentationImageData) {
45
52
  direction,
46
53
  spacing,
47
54
  }, viewPlaneNormal);
48
- const pointsBoundsLPS = getBoundingBoxAroundShapeWorld(points);
49
- let [[xMin, xMax], [yMin, yMax], [zMin, zMax]] = pointsBoundsLPS;
50
- xMin -= EPS;
51
- xMax += EPS;
52
- yMin -= EPS;
53
- yMax += EPS;
54
- zMin -= EPS;
55
- zMax += EPS;
56
- const pointInShapeFn = isAligned
57
- ? () => true
58
- : (pointLPS) => {
59
- const [x, y, z] = pointLPS;
60
- const xInside = x >= xMin && x <= xMax;
61
- const yInside = y >= yMin && y <= yMax;
62
- const zInside = z >= zMin && z <= zMax;
63
- return xInside && yInside && zInside;
64
- };
55
+ const pointInShapeFn = (pointLPS) => {
56
+ const v = vec3.create();
57
+ vec3.subtract(v, pointLPS, p0);
58
+ const u = vec3.dot(v, axisU);
59
+ const vproj = vec3.dot(v, axisV);
60
+ const d = Math.abs(vec3.dot(v, normal));
61
+ return (u >= -EPS &&
62
+ u <= uLen + EPS &&
63
+ vproj >= -EPS &&
64
+ vproj <= vLen + EPS &&
65
+ d <= EPS);
66
+ };
65
67
  return { boundsIJK, pointInShapeFn };
66
68
  }
67
69
  const RECTANGLE_STRATEGY = new BrushStrategy('Rectangle', compositions.regionFill, compositions.setValue, initializeRectangle, compositions.determineSegmentIndex, compositions.preview, compositions.labelmapStatistics);
@@ -3,7 +3,7 @@ import { vec3 } from 'gl-matrix';
3
3
  import BrushStrategy from './BrushStrategy';
4
4
  import compositions from './compositions';
5
5
  import StrategyCallbacks from '../../../enums/StrategyCallbacks';
6
- import { createEllipseInPoint } from './fillCircle';
6
+ import { createEllipseInPoint, getEllipseCornersFromCanvasCoordinates, } from './fillCircle';
7
7
  const { transformWorldToIndex } = csUtils;
8
8
  import { getSphereBoundsInfoFromViewport } from '../../../utilities/getSphereBoundsInfo';
9
9
  const sphereComposition = {
@@ -12,20 +12,22 @@ const sphereComposition = {
12
12
  if (!points) {
13
13
  return;
14
14
  }
15
- const center = vec3.fromValues(0, 0, 0);
16
- points.forEach((point) => {
17
- vec3.add(center, center, point);
18
- });
19
- vec3.scale(center, center, 1 / points.length);
15
+ const center = vec3.create();
16
+ if (points.length >= 2) {
17
+ vec3.add(center, points[0], points[1]);
18
+ vec3.scale(center, center, 0.5);
19
+ }
20
+ else {
21
+ vec3.copy(center, points[0]);
22
+ }
20
23
  operationData.centerWorld = center;
21
24
  operationData.centerIJK = transformWorldToIndex(segmentationImageData, center);
22
- const { boundsIJK: newBoundsIJK, topLeftWorld, bottomRightWorld, } = getSphereBoundsInfoFromViewport(points.slice(0, 2), segmentationImageData, viewport);
25
+ const { boundsIJK: newBoundsIJK } = getSphereBoundsInfoFromViewport(points.slice(0, 2), segmentationImageData, viewport);
26
+ const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
27
+ const corners = getEllipseCornersFromCanvasCoordinates(canvasCoordinates);
28
+ const cornersInWorld = corners.map((corner) => viewport.canvasToWorld(corner));
23
29
  operationData.isInObjectBoundsIJK = newBoundsIJK;
24
- operationData.isInObject = createEllipseInPoint({
25
- topLeftWorld,
26
- bottomRightWorld,
27
- center,
28
- });
30
+ operationData.isInObject = createEllipseInPoint(cornersInWorld);
29
31
  },
30
32
  };
31
33
  const SPHERE_STRATEGY = new BrushStrategy('Sphere', compositions.regionFill, compositions.setValue, sphereComposition, compositions.determineSegmentIndex, compositions.preview, compositions.labelmapStatistics, compositions.ensureSegmentationVolumeFor3DManipulation);