@cornerstonejs/tools 1.31.0 → 1.32.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.
Files changed (167) hide show
  1. package/dist/cjs/index.d.ts +2 -2
  2. package/dist/cjs/index.js +3 -2
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/stateManagement/annotation/AnnotationGroup.d.ts +18 -0
  5. package/dist/cjs/stateManagement/annotation/AnnotationGroup.js +73 -0
  6. package/dist/cjs/stateManagement/annotation/AnnotationGroup.js.map +1 -0
  7. package/dist/cjs/stateManagement/annotation/annotationState.js +1 -1
  8. package/dist/cjs/stateManagement/annotation/annotationState.js.map +1 -1
  9. package/dist/cjs/stateManagement/annotation/index.d.ts +2 -1
  10. package/dist/cjs/stateManagement/annotation/index.js +3 -1
  11. package/dist/cjs/stateManagement/annotation/index.js.map +1 -1
  12. package/dist/cjs/store/ToolGroupManager/ToolGroup.d.ts +1 -0
  13. package/dist/cjs/store/ToolGroupManager/ToolGroup.js +3 -1
  14. package/dist/cjs/store/ToolGroupManager/ToolGroup.js.map +1 -1
  15. package/dist/cjs/synchronizers/callbacks/stackImageSyncCallback.js +1 -1
  16. package/dist/cjs/synchronizers/callbacks/stackImageSyncCallback.js.map +1 -1
  17. package/dist/cjs/tools/annotation/KeyImageTool.d.ts +62 -0
  18. package/dist/cjs/tools/annotation/KeyImageTool.js +212 -0
  19. package/dist/cjs/tools/annotation/KeyImageTool.js.map +1 -0
  20. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js +2 -5
  21. package/dist/cjs/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  22. package/dist/cjs/tools/annotation/ProbeTool.js +15 -4
  23. package/dist/cjs/tools/annotation/ProbeTool.js.map +1 -1
  24. package/dist/cjs/tools/annotation/VideoRedactionTool.js +4 -1
  25. package/dist/cjs/tools/annotation/VideoRedactionTool.js.map +1 -1
  26. package/dist/cjs/tools/base/AnnotationDisplayTool.js +4 -2
  27. package/dist/cjs/tools/base/AnnotationDisplayTool.js.map +1 -1
  28. package/dist/cjs/tools/base/AnnotationTool.js +2 -7
  29. package/dist/cjs/tools/base/AnnotationTool.js.map +1 -1
  30. package/dist/cjs/tools/base/BaseTool.js +9 -1
  31. package/dist/cjs/tools/base/BaseTool.js.map +1 -1
  32. package/dist/cjs/tools/index.d.ts +5 -4
  33. package/dist/cjs/tools/index.js +9 -7
  34. package/dist/cjs/tools/index.js.map +1 -1
  35. package/dist/cjs/types/CalculatorTypes.d.ts +1 -1
  36. package/dist/cjs/utilities/annotationFrameRange.d.ts +13 -0
  37. package/dist/cjs/utilities/annotationFrameRange.js +46 -0
  38. package/dist/cjs/utilities/annotationFrameRange.js.map +1 -0
  39. package/dist/cjs/utilities/index.d.ts +2 -1
  40. package/dist/cjs/utilities/index.js +3 -1
  41. package/dist/cjs/utilities/index.js.map +1 -1
  42. package/dist/cjs/utilities/math/basic/BasicStatsCalculator.d.ts +0 -1
  43. package/dist/cjs/utilities/math/basic/BasicStatsCalculator.js +35 -22
  44. package/dist/cjs/utilities/math/basic/BasicStatsCalculator.js.map +1 -1
  45. package/dist/cjs/utilities/math/ellipse/pointInEllipse.d.ts +2 -1
  46. package/dist/cjs/utilities/math/ellipse/pointInEllipse.js.map +1 -1
  47. package/dist/cjs/utilities/math/sphere/pointInSphere.d.ts +1 -1
  48. package/dist/cjs/utilities/math/sphere/pointInSphere.js +3 -3
  49. package/dist/cjs/utilities/math/sphere/pointInSphere.js.map +1 -1
  50. package/dist/cjs/utilities/planar/filterAnnotationsForDisplay.js +19 -2
  51. package/dist/cjs/utilities/planar/filterAnnotationsForDisplay.js.map +1 -1
  52. package/dist/cjs/utilities/pointInShapeCallback.d.ts +6 -5
  53. package/dist/cjs/utilities/pointInShapeCallback.js +27 -26
  54. package/dist/cjs/utilities/pointInShapeCallback.js.map +1 -1
  55. package/dist/cjs/utilities/roundNumber.d.ts +1 -1
  56. package/dist/cjs/utilities/roundNumber.js +3 -0
  57. package/dist/cjs/utilities/roundNumber.js.map +1 -1
  58. package/dist/cjs/utilities/viewport/isViewportPreScaled.js +1 -1
  59. package/dist/cjs/utilities/viewport/isViewportPreScaled.js.map +1 -1
  60. package/dist/esm/index.js +2 -2
  61. package/dist/esm/index.js.map +1 -1
  62. package/dist/esm/stateManagement/annotation/AnnotationGroup.js +70 -0
  63. package/dist/esm/stateManagement/annotation/AnnotationGroup.js.map +1 -0
  64. package/dist/esm/stateManagement/annotation/annotationState.js +1 -1
  65. package/dist/esm/stateManagement/annotation/annotationState.js.map +1 -1
  66. package/dist/esm/stateManagement/annotation/index.js +2 -1
  67. package/dist/esm/stateManagement/annotation/index.js.map +1 -1
  68. package/dist/esm/store/ToolGroupManager/ToolGroup.js +3 -1
  69. package/dist/esm/store/ToolGroupManager/ToolGroup.js.map +1 -1
  70. package/dist/esm/synchronizers/callbacks/stackImageSyncCallback.js +1 -1
  71. package/dist/esm/synchronizers/callbacks/stackImageSyncCallback.js.map +1 -1
  72. package/dist/esm/tools/annotation/KeyImageTool.js +207 -0
  73. package/dist/esm/tools/annotation/KeyImageTool.js.map +1 -0
  74. package/dist/esm/tools/annotation/PlanarFreehandROITool.js +3 -6
  75. package/dist/esm/tools/annotation/PlanarFreehandROITool.js.map +1 -1
  76. package/dist/esm/tools/annotation/ProbeTool.js +17 -6
  77. package/dist/esm/tools/annotation/ProbeTool.js.map +1 -1
  78. package/dist/esm/tools/annotation/VideoRedactionTool.js +4 -1
  79. package/dist/esm/tools/annotation/VideoRedactionTool.js.map +1 -1
  80. package/dist/esm/tools/base/AnnotationDisplayTool.js +4 -2
  81. package/dist/esm/tools/base/AnnotationDisplayTool.js.map +1 -1
  82. package/dist/esm/tools/base/AnnotationTool.js +3 -8
  83. package/dist/esm/tools/base/AnnotationTool.js.map +1 -1
  84. package/dist/esm/tools/base/BaseTool.js +9 -1
  85. package/dist/esm/tools/base/BaseTool.js.map +1 -1
  86. package/dist/esm/tools/index.js +5 -4
  87. package/dist/esm/tools/index.js.map +1 -1
  88. package/dist/esm/utilities/annotationFrameRange.js +43 -0
  89. package/dist/esm/utilities/annotationFrameRange.js.map +1 -0
  90. package/dist/esm/utilities/index.js +2 -1
  91. package/dist/esm/utilities/index.js.map +1 -1
  92. package/dist/esm/utilities/math/basic/BasicStatsCalculator.js +35 -22
  93. package/dist/esm/utilities/math/basic/BasicStatsCalculator.js.map +1 -1
  94. package/dist/esm/utilities/math/ellipse/pointInEllipse.js.map +1 -1
  95. package/dist/esm/utilities/math/sphere/pointInSphere.js +3 -3
  96. package/dist/esm/utilities/math/sphere/pointInSphere.js.map +1 -1
  97. package/dist/esm/utilities/planar/filterAnnotationsForDisplay.js +19 -2
  98. package/dist/esm/utilities/planar/filterAnnotationsForDisplay.js.map +1 -1
  99. package/dist/esm/utilities/pointInShapeCallback.js +27 -26
  100. package/dist/esm/utilities/pointInShapeCallback.js.map +1 -1
  101. package/dist/esm/utilities/roundNumber.js +3 -0
  102. package/dist/esm/utilities/roundNumber.js.map +1 -1
  103. package/dist/esm/utilities/viewport/isViewportPreScaled.js +1 -1
  104. package/dist/esm/utilities/viewport/isViewportPreScaled.js.map +1 -1
  105. package/dist/types/index.d.ts +2 -2
  106. package/dist/types/index.d.ts.map +1 -1
  107. package/dist/types/stateManagement/annotation/AnnotationGroup.d.ts +19 -0
  108. package/dist/types/stateManagement/annotation/AnnotationGroup.d.ts.map +1 -0
  109. package/dist/types/stateManagement/annotation/index.d.ts +2 -1
  110. package/dist/types/stateManagement/annotation/index.d.ts.map +1 -1
  111. package/dist/types/store/ToolGroupManager/ToolGroup.d.ts +1 -0
  112. package/dist/types/store/ToolGroupManager/ToolGroup.d.ts.map +1 -1
  113. package/dist/types/synchronizers/callbacks/stackImageSyncCallback.d.ts.map +1 -1
  114. package/dist/types/tools/annotation/KeyImageTool.d.ts +63 -0
  115. package/dist/types/tools/annotation/KeyImageTool.d.ts.map +1 -0
  116. package/dist/types/tools/annotation/PlanarFreehandROITool.d.ts.map +1 -1
  117. package/dist/types/tools/annotation/ProbeTool.d.ts.map +1 -1
  118. package/dist/types/tools/annotation/VideoRedactionTool.d.ts.map +1 -1
  119. package/dist/types/tools/base/AnnotationDisplayTool.d.ts.map +1 -1
  120. package/dist/types/tools/base/AnnotationTool.d.ts.map +1 -1
  121. package/dist/types/tools/base/BaseTool.d.ts.map +1 -1
  122. package/dist/types/tools/index.d.ts +5 -4
  123. package/dist/types/tools/index.d.ts.map +1 -1
  124. package/dist/types/types/CalculatorTypes.d.ts +1 -1
  125. package/dist/types/types/CalculatorTypes.d.ts.map +1 -1
  126. package/dist/types/utilities/annotationFrameRange.d.ts +14 -0
  127. package/dist/types/utilities/annotationFrameRange.d.ts.map +1 -0
  128. package/dist/types/utilities/index.d.ts +2 -1
  129. package/dist/types/utilities/index.d.ts.map +1 -1
  130. package/dist/types/utilities/math/basic/BasicStatsCalculator.d.ts +0 -1
  131. package/dist/types/utilities/math/basic/BasicStatsCalculator.d.ts.map +1 -1
  132. package/dist/types/utilities/math/ellipse/pointInEllipse.d.ts +2 -1
  133. package/dist/types/utilities/math/ellipse/pointInEllipse.d.ts.map +1 -1
  134. package/dist/types/utilities/math/sphere/pointInSphere.d.ts +1 -1
  135. package/dist/types/utilities/math/sphere/pointInSphere.d.ts.map +1 -1
  136. package/dist/types/utilities/planar/filterAnnotationsForDisplay.d.ts.map +1 -1
  137. package/dist/types/utilities/pointInShapeCallback.d.ts +6 -5
  138. package/dist/types/utilities/pointInShapeCallback.d.ts.map +1 -1
  139. package/dist/types/utilities/roundNumber.d.ts +1 -1
  140. package/dist/types/utilities/roundNumber.d.ts.map +1 -1
  141. package/dist/umd/index.js +1 -1
  142. package/dist/umd/index.js.map +1 -1
  143. package/package.json +3 -3
  144. package/src/index.ts +2 -0
  145. package/src/stateManagement/annotation/AnnotationGroup.ts +120 -0
  146. package/src/stateManagement/annotation/annotationState.ts +1 -1
  147. package/src/stateManagement/annotation/index.ts +2 -0
  148. package/src/store/ToolGroupManager/ToolGroup.ts +10 -1
  149. package/src/synchronizers/callbacks/stackImageSyncCallback.ts +2 -1
  150. package/src/tools/annotation/KeyImageTool.ts +435 -0
  151. package/src/tools/annotation/PlanarFreehandROITool.ts +4 -6
  152. package/src/tools/annotation/ProbeTool.ts +19 -6
  153. package/src/tools/annotation/VideoRedactionTool.ts +10 -1
  154. package/src/tools/base/AnnotationDisplayTool.ts +3 -4
  155. package/src/tools/base/AnnotationTool.ts +3 -6
  156. package/src/tools/base/BaseTool.ts +18 -2
  157. package/src/tools/index.ts +8 -5
  158. package/src/types/CalculatorTypes.ts +1 -1
  159. package/src/utilities/annotationFrameRange.ts +78 -0
  160. package/src/utilities/index.ts +2 -0
  161. package/src/utilities/math/basic/BasicStatsCalculator.ts +51 -23
  162. package/src/utilities/math/ellipse/pointInEllipse.ts +2 -1
  163. package/src/utilities/math/sphere/pointInSphere.ts +4 -7
  164. package/src/utilities/planar/filterAnnotationsForDisplay.ts +29 -3
  165. package/src/utilities/pointInShapeCallback.ts +46 -38
  166. package/src/utilities/roundNumber.ts +7 -1
  167. package/src/utilities/viewport/isViewportPreScaled.ts +1 -1
@@ -7,7 +7,6 @@ import {
7
7
  triggerEvent,
8
8
  eventTarget,
9
9
  utilities as csUtils,
10
- utilities,
11
10
  } from '@cornerstonejs/core';
12
11
  import type { Types } from '@cornerstonejs/core';
13
12
 
@@ -24,6 +23,7 @@ import {
24
23
  import { state } from '../../store';
25
24
  import { Events } from '../../enums';
26
25
  import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
26
+ import roundNumber from '../../utilities/roundNumber';
27
27
  import {
28
28
  resetElementCursor,
29
29
  hideElementCursor,
@@ -578,20 +578,33 @@ class ProbeTool extends AnnotationTool {
578
578
  index[1] = Math.round(index[1]);
579
579
  index[2] = Math.round(index[2]);
580
580
 
581
+ const samplesPerPixel =
582
+ scalarData.length / dimensions[2] / dimensions[1] / dimensions[0];
583
+
581
584
  if (csUtils.indexWithinDimensions(index, dimensions)) {
582
585
  this.isHandleOutsideImage = false;
583
- const yMultiple = dimensions[0];
584
- const zMultiple = dimensions[0] * dimensions[1];
586
+ const yMultiple = dimensions[0] * samplesPerPixel;
587
+ const zMultiple = dimensions[0] * dimensions[1] * samplesPerPixel;
585
588
 
589
+ const baseIndex =
590
+ index[2] * zMultiple +
591
+ index[1] * yMultiple +
592
+ index[0] * samplesPerPixel;
586
593
  const value =
587
- scalarData[index[2] * zMultiple + index[1] * yMultiple + index[0]];
594
+ samplesPerPixel > 2
595
+ ? [
596
+ scalarData[baseIndex],
597
+ scalarData[baseIndex + 1],
598
+ scalarData[baseIndex + 2],
599
+ ]
600
+ : scalarData[baseIndex];
588
601
 
589
602
  // Index[2] for stackViewport is always 0, but for visualization
590
603
  // we reset it to be imageId index
591
604
  if (targetId.startsWith('imageId:')) {
592
605
  const imageId = targetId.split('imageId:')[1];
593
606
  const imageURI = csUtils.imageIdToURI(imageId);
594
- const viewports = utilities.getViewportsWithImageURI(
607
+ const viewports = csUtils.getViewportsWithImageURI(
595
608
  imageURI,
596
609
  renderingEngineId
597
610
  );
@@ -651,7 +664,7 @@ function defaultGetTextLines(data, targetId): string[] {
651
664
 
652
665
  textLines.push(`(${index[0]}, ${index[1]}, ${index[2]})`);
653
666
 
654
- textLines.push(`${value.toFixed(2)} ${modalityUnit}`);
667
+ textLines.push(`${roundNumber(value)} ${modalityUnit}`);
655
668
 
656
669
  return textLines;
657
670
  }
@@ -73,6 +73,15 @@ class VideoRedactionTool extends AnnotationTool {
73
73
 
74
74
  this.isDrawing = true;
75
75
 
76
+ const camera = viewport.getCamera();
77
+ const { viewPlaneNormal, viewUp } = camera;
78
+ const referencedImageId = this.getReferencedImageId(
79
+ viewport,
80
+ worldPos,
81
+ viewPlaneNormal,
82
+ viewUp
83
+ );
84
+
76
85
  const annotation = {
77
86
  metadata: {
78
87
  // We probably just want a different type of data here, hacking this
@@ -80,7 +89,7 @@ class VideoRedactionTool extends AnnotationTool {
80
89
  viewPlaneNormal: <Types.Point3>[0, 0, 1],
81
90
  viewUp: <Types.Point3>[0, 1, 0],
82
91
  FrameOfReferenceUID: viewport.getFrameOfReferenceUID(),
83
- referencedImageId: viewport.getFrameOfReferenceUID(),
92
+ referencedImageId,
84
93
  toolName: this.getToolName(),
85
94
  },
86
95
  data: {
@@ -132,11 +132,10 @@ abstract class AnnotationDisplayTool extends BaseTool {
132
132
 
133
133
  let referencedImageId;
134
134
 
135
- if (
136
- viewport instanceof StackViewport ||
137
- viewport instanceof VideoViewport
138
- ) {
135
+ if (viewport instanceof StackViewport) {
139
136
  referencedImageId = targetId.split('imageId:')[1];
137
+ } else if (viewport instanceof VideoViewport) {
138
+ referencedImageId = targetId.split('videoId:')[1];
140
139
  } else {
141
140
  const volumeId = targetId.split('volumeId:')[1];
142
141
  const imageVolume = cache.getVolume(volumeId);
@@ -305,13 +305,10 @@ abstract class AnnotationTool extends AnnotationDisplayTool {
305
305
  const volumeId = targetId.split('volumeId:')[1];
306
306
  const volume = cache.getVolume(volumeId);
307
307
  return volume.scaling?.PT !== undefined;
308
- } else if (viewport instanceof StackViewport) {
309
- const scalingModule: Types.ScalingParameters | undefined =
310
- imageId && metaData.get('scalingModule', imageId);
311
- return typeof scalingModule?.suvbw === 'number';
312
- } else {
313
- throw new Error('Viewport is not a valid type');
314
308
  }
309
+ const scalingModule: Types.ScalingParameters | undefined =
310
+ imageId && metaData.get('scalingModule', imageId);
311
+ return typeof scalingModule?.suvbw === 'number';
315
312
  }
316
313
 
317
314
  /**
@@ -141,7 +141,10 @@ abstract class BaseTool implements IBaseTool {
141
141
 
142
142
  /**
143
143
  * Get the image that is displayed for the targetId in the cachedStats
144
- * which can be either imageId:<imageId> or volumeId:<volumeId>
144
+ * which can be
145
+ * * imageId:<imageId>
146
+ * * volumeId:<volumeId>
147
+ * * videoId:<basePathForVideo>/frames/<frameSpecifier>
145
148
  *
146
149
  * @param targetId - annotation targetId stored in the cached stats
147
150
  * @param renderingEngine - The rendering engine
@@ -183,6 +186,19 @@ abstract class BaseTool implements IBaseTool {
183
186
  return;
184
187
  }
185
188
 
189
+ return viewports[0].getImageData();
190
+ } else if (targetId.startsWith('videoId:')) {
191
+ // Video id can be multi-valued for the frame information
192
+ const imageURI = utilities.imageIdToURI(targetId);
193
+ const viewports = utilities.getViewportsWithImageURI(
194
+ imageURI,
195
+ renderingEngine.id
196
+ );
197
+
198
+ if (!viewports || !viewports.length) {
199
+ return;
200
+ }
201
+
186
202
  return viewports[0].getImageData();
187
203
  } else {
188
204
  throw new Error(
@@ -207,7 +223,7 @@ abstract class BaseTool implements IBaseTool {
207
223
  } else if (viewport instanceof BaseVolumeViewport) {
208
224
  return `volumeId:${this.getTargetVolumeId(viewport)}`;
209
225
  } else if (viewport instanceof VideoViewport) {
210
- return '';
226
+ return `videoId:${viewport.getCurrentImageId()}`;
211
227
  } else {
212
228
  throw new Error(
213
229
  'getTargetId: viewport must be a StackViewport or VolumeViewport'
@@ -14,7 +14,11 @@ import AdvancedMagnifyTool from './AdvancedMagnifyTool';
14
14
  import ReferenceLinesTool from './ReferenceLinesTool';
15
15
  import OverlayGridTool from './OverlayGridTool';
16
16
  import SegmentationIntersectionTool from './SegmentationIntersectionTool';
17
- //
17
+ import ReferenceCursors from './ReferenceCursors';
18
+ import ReferenceLines from './ReferenceLinesTool';
19
+ import ScaleOverlayTool from './ScaleOverlayTool';
20
+
21
+ // Annotation tools
18
22
  import BidirectionalTool from './annotation/BidirectionalTool';
19
23
  import LengthTool from './annotation/LengthTool';
20
24
  import ProbeTool from './annotation/ProbeTool';
@@ -26,9 +30,7 @@ import PlanarFreehandROITool from './annotation/PlanarFreehandROITool';
26
30
  import ArrowAnnotateTool from './annotation/ArrowAnnotateTool';
27
31
  import AngleTool from './annotation/AngleTool';
28
32
  import CobbAngleTool from './annotation/CobbAngleTool';
29
- import ReferenceCursors from './ReferenceCursors';
30
- import ReferenceLines from './ReferenceLinesTool';
31
- import ScaleOverlayTool from './ScaleOverlayTool';
33
+ import KeyImageTool from './annotation/KeyImageTool';
32
34
 
33
35
  // Segmentation DisplayTool
34
36
  import SegmentationDisplayTool from './displayTools/SegmentationDisplayTool';
@@ -59,6 +61,7 @@ export {
59
61
  ZoomTool,
60
62
  VolumeRotateMouseWheelTool,
61
63
  MIPJumpToClickTool,
64
+ ReferenceCursors,
62
65
  // Annotation Tools
63
66
  CrosshairsTool,
64
67
  ReferenceLinesTool,
@@ -74,7 +77,7 @@ export {
74
77
  ArrowAnnotateTool,
75
78
  AngleTool,
76
79
  CobbAngleTool,
77
- ReferenceCursors,
80
+ KeyImageTool,
78
81
  // Segmentations Display
79
82
  SegmentationDisplayTool,
80
83
  // Segmentations Tools
@@ -1,6 +1,6 @@
1
1
  type Statistics = {
2
2
  name: string;
3
- value: number;
3
+ value: number | number[];
4
4
  unit: null | string;
5
5
  };
6
6
 
@@ -0,0 +1,78 @@
1
+ import { triggerEvent, eventTarget } from '@cornerstonejs/core';
2
+ import Events from '../enums/Events';
3
+ import { Annotation } from '../types';
4
+
5
+ export type FramesRange = [number, number] | number;
6
+
7
+ /**
8
+ * This class handles the annotation frame range values for multiframes.
9
+ * Mostly used for the Video viewport, it allows references to
10
+ * a range of frame values.
11
+ */
12
+ export default class AnnotationFrameRange {
13
+ protected static frameRangeExtractor =
14
+ /(\/frames\/|[&?]frameNumber=)([^/&?]*)/i;
15
+
16
+ protected static imageIdToFrames(imageId: string): FramesRange {
17
+ const match = imageId.match(this.frameRangeExtractor);
18
+ if (!match || !match[2]) {
19
+ return null;
20
+ }
21
+ const range = match[2].split('-').map((it) => Number(it));
22
+ if (range.length === 1) {
23
+ return range[0];
24
+ }
25
+ return range as FramesRange;
26
+ }
27
+
28
+ public static framesToString(range) {
29
+ if (Array.isArray(range)) {
30
+ return `${range[0]}-${range[1]}`;
31
+ }
32
+ return String(range);
33
+ }
34
+
35
+ protected static framesToImageId(
36
+ imageId: string,
37
+ range: FramesRange | string
38
+ ): string {
39
+ const match = imageId.match(this.frameRangeExtractor);
40
+ if (!match || !match[2]) {
41
+ return null;
42
+ }
43
+ const newRangeString = this.framesToString(range);
44
+ return imageId.replace(
45
+ this.frameRangeExtractor,
46
+ `${match[1]}${newRangeString}`
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Sets the range of frames to associate with the given annotation.
52
+ * The range can be a single frame number (1 based according to DICOM),
53
+ * or a range of values in the format `min-max` where min, max are inclusive
54
+ * Modifies the referencedImageID to specify the updated URL.
55
+ */
56
+ public static setFrameRange(
57
+ annotation: Annotation,
58
+ range: FramesRange | string,
59
+ eventBase?: { viewportId; renderingEngineId }
60
+ ) {
61
+ const { referencedImageId } = annotation.metadata;
62
+ annotation.metadata.referencedImageId = this.framesToImageId(
63
+ referencedImageId,
64
+ range
65
+ );
66
+ const eventDetail = {
67
+ ...eventBase,
68
+ annotation,
69
+ };
70
+ triggerEvent(eventTarget, Events.ANNOTATION_MODIFIED, eventDetail);
71
+ }
72
+
73
+ public static getFrameRange(
74
+ annotation: Annotation
75
+ ): number | [number, number] {
76
+ return this.imageIdToFrames(annotation.metadata.referencedImageId);
77
+ }
78
+ }
@@ -20,6 +20,7 @@ import pointInSurroundingSphereCallback from './pointInSurroundingSphereCallback
20
20
  import scroll from './scroll';
21
21
  import roundNumber from './roundNumber';
22
22
  import { pointToString } from './pointToString';
23
+ import annotationFrameRange from './annotationFrameRange';
23
24
 
24
25
  // name spaces
25
26
  import * as segmentation from './segmentation';
@@ -78,4 +79,5 @@ export {
78
79
  pointToString,
79
80
  polyDataUtils,
80
81
  voi,
82
+ annotationFrameRange,
81
83
  };
@@ -2,11 +2,10 @@ import { Statistics } from '../../../types';
2
2
  import Calculator from './Calculator';
3
3
 
4
4
  export default class BasicStatsCalculator extends Calculator {
5
- private static max = -Infinity;
6
- private static currentMax = 0;
7
- private static sum = 0;
8
- private static sumSquares = 0;
9
- private static squaredDiffSum = 0;
5
+ private static max = [-Infinity];
6
+ private static sum = [0];
7
+ private static sumSquares = [0];
8
+ private static squaredDiffSum = [0];
10
9
  private static count = 0;
11
10
 
12
11
  /**
@@ -15,16 +14,34 @@ export default class BasicStatsCalculator extends Calculator {
15
14
  * @param value of the point in the shape of the annotation
16
15
  */
17
16
  static statsCallback = ({ value: newValue }): void => {
18
- if (newValue > this.max) {
19
- this.max = newValue;
20
- this.currentMax = newValue;
17
+ if (
18
+ Array.isArray(newValue) &&
19
+ newValue.length > 1 &&
20
+ this.max.length === 1
21
+ ) {
22
+ this.max.push(this.max[0], this.max[0]);
23
+ this.sum.push(this.sum[0], this.sum[0]);
24
+ this.sumSquares.push(this.sumSquares[0], this.sumSquares[0]);
25
+ this.squaredDiffSum.push(this.squaredDiffSum[0], this.squaredDiffSum[0]);
21
26
  }
22
27
 
28
+ const newArray = Array.isArray(newValue) ? newValue : [newValue];
23
29
  this.count += 1;
24
30
 
25
- this.sum += newValue;
26
- this.sumSquares += newValue ** 2;
27
- this.squaredDiffSum += Math.pow(newValue - this.sum / this.count, 2);
31
+ this.max.forEach(
32
+ (it, idx) => (this.max[idx] = Math.max(it, newArray[idx]))
33
+ );
34
+ this.sum.map((it, idx) => (this.sum[idx] += newArray[idx]));
35
+ this.sumSquares.map(
36
+ (it, idx) => (this.sumSquares[idx] += newArray[idx] ** 2)
37
+ );
38
+ this.squaredDiffSum.map(
39
+ (it, idx) =>
40
+ (this.squaredDiffSum[idx] += Math.pow(
41
+ newArray[idx] - this.sum[idx] / this.count,
42
+ 2
43
+ ))
44
+ );
28
45
  };
29
46
 
30
47
  /**
@@ -38,23 +55,34 @@ export default class BasicStatsCalculator extends Calculator {
38
55
  */
39
56
 
40
57
  static getStatistics = (): Statistics[] => {
41
- const mean = this.sum / this.count;
42
- const stdDev = Math.sqrt(this.squaredDiffSum / this.count);
43
- const stdDevWithSumSquare = Math.sqrt(
44
- this.sumSquares / this.count - mean ** 2
58
+ const mean = this.sum.map((sum) => sum / this.count);
59
+ const stdDev = this.squaredDiffSum.map((squaredDiffSum) =>
60
+ Math.sqrt(squaredDiffSum / this.count)
45
61
  );
62
+ const stdDevWithSumSquare = this.sumSquares.map((it, idx) =>
63
+ Math.sqrt(this.sumSquares[idx] / this.count - mean[idx] ** 2)
64
+ );
65
+ const currentMax = this.max;
46
66
 
47
- this.max = -Infinity;
48
- this.sum = 0;
49
- this.sumSquares = 0;
50
- this.squaredDiffSum = 0;
67
+ this.max = [-Infinity];
68
+ this.sum = [0];
69
+ this.sumSquares = [0];
70
+ this.squaredDiffSum = [0];
51
71
  this.count = 0;
52
72
 
53
73
  return [
54
- { name: 'max', value: this.currentMax, unit: null },
55
- { name: 'mean', value: mean, unit: null },
56
- { name: 'stdDev', value: stdDev, unit: null },
57
- { name: 'stdDevWithSumSquare', value: stdDevWithSumSquare, unit: null },
74
+ { name: 'max', value: singleArrayAsNumber(currentMax), unit: null },
75
+ { name: 'mean', value: singleArrayAsNumber(mean), unit: null },
76
+ { name: 'stdDev', value: singleArrayAsNumber(stdDev), unit: null },
77
+ {
78
+ name: 'stdDevWithSumSquare',
79
+ value: singleArrayAsNumber(stdDevWithSumSquare),
80
+ unit: null,
81
+ },
58
82
  ];
59
83
  };
60
84
  }
85
+
86
+ function singleArrayAsNumber(val: number[]) {
87
+ return val.length === 1 ? val[0] : val;
88
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
+ import { vec3 } from 'gl-matrix';
2
3
 
3
4
  type Ellipse = {
4
5
  center: Types.Point3;
@@ -15,7 +16,7 @@ type Ellipse = {
15
16
  */
16
17
  export default function pointInEllipse(
17
18
  ellipse: Ellipse,
18
- pointLPS: Types.Point3
19
+ pointLPS: vec3
19
20
  ): boolean {
20
21
  const { center: circleCenterWorld, xRadius, yRadius, zRadius } = ellipse;
21
22
  const [x, y, z] = pointLPS;
@@ -16,16 +16,13 @@ type Sphere = {
16
16
  * @param pointLPS - the point to check in world coordinates
17
17
  * @returns boolean
18
18
  */
19
- export default function pointInSphere(
20
- sphere: Sphere,
21
- pointLPS: Types.Point3
22
- ): boolean {
19
+ export default function pointInSphere(sphere: Sphere, pointLPS: vec3): boolean {
23
20
  const { center, radius } = sphere;
24
21
 
25
22
  return (
26
- (pointLPS[0] - center[0]) ** 2 +
27
- (pointLPS[1] - center[1]) ** 2 +
28
- (pointLPS[2] - center[2]) ** 2 <=
23
+ (pointLPS[0] - center[0]) * (pointLPS[0] - center[0]) +
24
+ (pointLPS[1] - center[1]) * (pointLPS[1] - center[1]) +
25
+ (pointLPS[2] - center[2]) * (pointLPS[2] - center[2]) <=
29
26
  radius ** 2
30
27
  );
31
28
  }
@@ -8,6 +8,9 @@ import {
8
8
 
9
9
  import filterAnnotationsWithinSlice from './filterAnnotationsWithinSlice';
10
10
  import { Annotations } from '../../types';
11
+ import { annotationFrameRange } from '..';
12
+
13
+ const baseUrlExtractor = /(videoId:|imageId:|volumeId:)([a-zA-Z]*:)/;
11
14
 
12
15
  /**
13
16
  * Given the viewport and the annotations, it filters the annotations array and only
@@ -43,7 +46,7 @@ export default function filterAnnotationsForDisplay(
43
46
 
44
47
  if (imageId === undefined) {
45
48
  // This annotation was not drawn on a non-coplanar reformat, and such does
46
- // note have a referenced imageId.
49
+ // not have a referenced imageId.
47
50
  return false;
48
51
  }
49
52
 
@@ -54,8 +57,31 @@ export default function filterAnnotationsForDisplay(
54
57
  } else if (viewport instanceof VideoViewport) {
55
58
  const frameOfReferenceUID: string = viewport.getFrameOfReferenceUID();
56
59
 
57
- return annotations.filter((toolData) => {
58
- return toolData.metadata.FrameOfReferenceUID === frameOfReferenceUID;
60
+ return annotations.filter((annotation) => {
61
+ if (!annotation.isVisible) {
62
+ return false;
63
+ }
64
+ if (annotation.metadata.FrameOfReferenceUID !== frameOfReferenceUID) {
65
+ return false;
66
+ }
67
+ const testURI = annotation.metadata.referencedImageId.replace(
68
+ baseUrlExtractor,
69
+ ''
70
+ );
71
+
72
+ if (!viewport.hasImageURI(testURI)) {
73
+ return false;
74
+ }
75
+ const range = annotationFrameRange.getFrameRange(annotation);
76
+ const frameNumber = viewport.getFrameNumber();
77
+ if (Array.isArray(range)) {
78
+ return frameNumber >= range[0] && frameNumber <= range[1];
79
+ }
80
+ // Arbitrary 5 frames of slop on the video for matching single frame
81
+ // number to position - this allows the annotation to display when
82
+ // the video element is not exactly the same timing as expected or when
83
+ // playing video back.
84
+ return Math.abs(frameNumber - range) <= 5;
59
85
  });
60
86
  } else if (viewport instanceof VolumeViewport) {
61
87
  const camera = viewport.getCamera();
@@ -6,8 +6,8 @@ import BoundsIJK from '../types/BoundsIJK';
6
6
  export type PointInShape = {
7
7
  value: number;
8
8
  index: number;
9
- pointIJK: Types.Point3;
10
- pointLPS: Types.Point3;
9
+ pointIJK: vec3;
10
+ pointLPS: vec3;
11
11
  };
12
12
 
13
13
  export type PointInShapeCallback = ({
@@ -18,14 +18,11 @@ export type PointInShapeCallback = ({
18
18
  }: {
19
19
  value: number;
20
20
  index: number;
21
- pointIJK: Types.Point3;
22
- pointLPS: Types.Point3;
21
+ pointIJK: vec3;
22
+ pointLPS: vec3;
23
23
  }) => void;
24
24
 
25
- export type ShapeFnCriteria = (
26
- pointIJK: Types.Point3,
27
- pointLPS: Types.Point3
28
- ) => boolean;
25
+ export type ShapeFnCriteria = (pointIJK: vec3, pointLPS: vec3) => boolean;
29
26
 
30
27
  /**
31
28
  * For each point in the image (If boundsIJK is not provided, otherwise, for each
@@ -105,46 +102,57 @@ export default function pointInShapeCallback(
105
102
  scanAxisNormal[2] * scanAxisSpacing
106
103
  );
107
104
 
108
- const yMultiple = dimensions[0];
109
- const zMultiple = dimensions[0] * dimensions[1];
105
+ const xMultiple =
106
+ scalarData.length / dimensions[2] / dimensions[1] / dimensions[0];
107
+ const yMultiple = dimensions[0] * xMultiple;
108
+ const zMultiple = dimensions[1] * yMultiple;
110
109
 
111
110
  const pointsInShape: Array<PointInShape> = [];
111
+
112
+ const currentPos = vec3.clone(worldPosStart);
113
+
112
114
  for (let k = kMin; k <= kMax; k++) {
115
+ const startPosJ = vec3.clone(currentPos);
116
+
113
117
  for (let j = jMin; j <= jMax; j++) {
118
+ const startPosI = vec3.clone(currentPos);
119
+
114
120
  for (let i = iMin; i <= iMax; i++) {
115
121
  const pointIJK: Types.Point3 = [i, j, k];
116
- const dI = i - iMin;
117
- const dJ = j - jMin;
118
- const dK = k - kMin;
119
-
120
- const startWorld = worldPosStart;
121
-
122
- const pointLPS: Types.Point3 = [
123
- startWorld[0] +
124
- dI * rowStep[0] +
125
- dJ * columnStep[0] +
126
- dK * scanAxisStep[0],
127
- startWorld[1] +
128
- dI * rowStep[1] +
129
- dJ * columnStep[1] +
130
- dK * scanAxisStep[1],
131
- startWorld[2] +
132
- dI * rowStep[2] +
133
- dJ * columnStep[2] +
134
- dK * scanAxisStep[2],
135
- ];
136
-
137
- if (pointInShapeFn(pointLPS, pointIJK)) {
138
- const index = k * zMultiple + j * yMultiple + i;
139
- const value = scalarData[index];
140
-
141
- pointsInShape.push({ value, index, pointIJK, pointLPS });
142
- if (callback !== null) {
143
- callback({ value, index, pointIJK, pointLPS });
122
+
123
+ // The current world position (pointLPS) is now in currentPos
124
+ if (pointInShapeFn(currentPos as Types.Point3, currentPos)) {
125
+ const index = k * zMultiple + j * yMultiple + i * xMultiple;
126
+ let value;
127
+ if (xMultiple > 2) {
128
+ value = [
129
+ scalarData[index],
130
+ scalarData[index + 1],
131
+ scalarData[index + 2],
132
+ ];
133
+ } else {
134
+ value = scalarData[index];
135
+ }
136
+
137
+ pointsInShape.push({ value, index, pointIJK, pointLPS: currentPos });
138
+ if (callback) {
139
+ callback({ value, index, pointIJK, pointLPS: currentPos });
144
140
  }
145
141
  }
142
+
143
+ // Increment currentPos by rowStep for the next iteration
144
+ vec3.add(currentPos, currentPos, rowStep);
146
145
  }
146
+
147
+ // Reset currentPos to the start of the next J line and increment by columnStep
148
+ vec3.copy(currentPos, startPosI);
149
+ vec3.add(currentPos, currentPos, columnStep);
147
150
  }
151
+
152
+ // Reset currentPos to the start of the next K slice and increment by scanAxisStep
153
+ vec3.copy(currentPos, startPosJ);
154
+ vec3.add(currentPos, currentPos, scanAxisStep);
148
155
  }
156
+
149
157
  return pointsInShape;
150
158
  }
@@ -10,7 +10,13 @@
10
10
  * @param value - to return a fixed measurement value from
11
11
  * @param precision - defining how many digits after 1..9 are desired
12
12
  */
13
- function roundNumber(value: string | number, precision = 2): string {
13
+ function roundNumber(
14
+ value: string | number | (string | number)[],
15
+ precision = 2
16
+ ): string {
17
+ if (Array.isArray(value)) {
18
+ return value.map((v) => roundNumber(v, precision)).join(', ');
19
+ }
14
20
  if (value === undefined || value === null || value === '') {
15
21
  return 'NaN';
16
22
  }
@@ -19,7 +19,7 @@ function isViewportPreScaled(
19
19
  const { preScale } = viewport.getImageData() || {};
20
20
  return !!preScale?.scaled;
21
21
  } else {
22
- throw new Error('Viewport is not a valid type');
22
+ return false;
23
23
  }
24
24
  }
25
25