@cornerstonejs/tools 2.8.6 → 2.10.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.
Files changed (63) hide show
  1. package/dist/esm/eventDispatchers/shared/getToolsWithActionsForKeyboardEvents.js +1 -1
  2. package/dist/esm/eventDispatchers/shared/getToolsWithActionsForMouseEvent.js +1 -1
  3. package/dist/esm/index.d.ts +2 -2
  4. package/dist/esm/index.js +2 -2
  5. package/dist/esm/stateManagement/segmentation/activeSegmentation.d.ts +1 -1
  6. package/dist/esm/stateManagement/segmentation/activeSegmentation.js +1 -1
  7. package/dist/esm/stateManagement/segmentation/index.d.ts +3 -1
  8. package/dist/esm/stateManagement/segmentation/index.js +3 -1
  9. package/dist/esm/stateManagement/segmentation/internalAddSegmentationRepresentation.js +1 -0
  10. package/dist/esm/stateManagement/segmentation/polySeg/Contour/utils/createAndAddContourSegmentationsFromClippedSurfaces.js +1 -1
  11. package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js +4 -2
  12. package/dist/esm/tools/annotation/RegionSegmentPlusTool.d.ts +3 -2
  13. package/dist/esm/tools/annotation/RegionSegmentPlusTool.js +27 -12
  14. package/dist/esm/tools/annotation/RegionSegmentTool.d.ts +1 -1
  15. package/dist/esm/tools/annotation/RegionSegmentTool.js +7 -8
  16. package/dist/esm/tools/annotation/SplineROITool.js +7 -1
  17. package/dist/esm/tools/annotation/WholeBodySegmentTool.d.ts +3 -2
  18. package/dist/esm/tools/annotation/WholeBodySegmentTool.js +40 -5
  19. package/dist/esm/tools/base/AnnotationTool.d.ts +2 -1
  20. package/dist/esm/tools/base/AnnotationTool.js +6 -3
  21. package/dist/esm/tools/base/ContourSegmentationBaseTool.d.ts +1 -0
  22. package/dist/esm/tools/base/ContourSegmentationBaseTool.js +1 -0
  23. package/dist/esm/tools/base/GrowCutBaseTool.d.ts +25 -10
  24. package/dist/esm/tools/base/GrowCutBaseTool.js +124 -21
  25. package/dist/esm/tools/displayTools/Labelmap/addLabelmapToElement.d.ts +5 -1
  26. package/dist/esm/tools/displayTools/Labelmap/addLabelmapToElement.js +17 -3
  27. package/dist/esm/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.d.ts +10 -0
  28. package/dist/esm/tools/displayTools/Labelmap/addVolumesAsIndependentComponents.js +123 -0
  29. package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.d.ts +1 -0
  30. package/dist/esm/tools/displayTools/Labelmap/labelmapDisplay.js +17 -10
  31. package/dist/esm/tools/index.d.ts +3 -1
  32. package/dist/esm/tools/index.js +3 -1
  33. package/dist/esm/tools/segmentation/BrushTool.d.ts +0 -1
  34. package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +5 -1
  35. package/dist/esm/tools/segmentation/LabelmapBaseTool.js +107 -12
  36. package/dist/esm/tools/segmentation/SegmentSelectTool.js +0 -1
  37. package/dist/esm/tools/segmentation/strategies/compositions/index.js +1 -1
  38. package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.d.ts +5 -0
  39. package/dist/esm/tools/segmentation/strategies/compositions/islandRemovalComposition.js +27 -0
  40. package/dist/esm/tools/segmentation/strategies/compositions/labelmapStatistics.js +69 -2
  41. package/dist/esm/types/CalculatorTypes.d.ts +4 -0
  42. package/dist/esm/types/SegmentationStateTypes.d.ts +3 -1
  43. package/dist/esm/utilities/contourSegmentation/addContourSegmentationAnnotation.js +5 -2
  44. package/dist/esm/utilities/index.d.ts +3 -1
  45. package/dist/esm/utilities/index.js +3 -1
  46. package/dist/esm/utilities/planar/filterAnnotationsWithinSlice.js +2 -2
  47. package/dist/esm/utilities/planar/getPointInLineOfSightWithCriteria.d.ts +5 -1
  48. package/dist/esm/utilities/planar/getPointInLineOfSightWithCriteria.js +40 -29
  49. package/dist/esm/utilities/planar/index.d.ts +3 -2
  50. package/dist/esm/utilities/planar/index.js +3 -2
  51. package/dist/esm/utilities/segmentation/VolumetricCalculator.d.ts +7 -0
  52. package/dist/esm/utilities/segmentation/VolumetricCalculator.js +28 -0
  53. package/dist/esm/utilities/segmentation/growCut/runGrowCutForBoundingBox.d.ts +0 -2
  54. package/dist/esm/utilities/segmentation/growCut/runOneClickGrowCut.d.ts +0 -4
  55. package/dist/esm/utilities/segmentation/index.d.ts +2 -1
  56. package/dist/esm/utilities/segmentation/index.js +2 -1
  57. package/dist/esm/utilities/segmentation/islandRemoval.d.ts +28 -0
  58. package/dist/esm/utilities/segmentation/islandRemoval.js +181 -0
  59. package/package.json +3 -3
  60. package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.d.ts +0 -16
  61. package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js +0 -159
  62. /package/dist/esm/{tools/segmentation/strategies/utils → utilities}/normalizeViewportPlane.d.ts +0 -0
  63. /package/dist/esm/{tools/segmentation/strategies/utils → utilities}/normalizeViewportPlane.js +0 -0
@@ -11,17 +11,25 @@ import { getSegmentIndexColor } from '../../stateManagement/segmentation/config/
11
11
  import { getActiveSegmentIndex } from '../../stateManagement/segmentation/getActiveSegmentIndex';
12
12
  import { StrategyCallbacks } from '../../enums';
13
13
  import * as LabelmapMemo from '../../utilities/segmentation/createLabelmapMemo';
14
+ import { getAllAnnotations, removeAnnotation, } from '../../stateManagement/annotation/annotationState';
15
+ import { filterAnnotationsForDisplay } from '../../utilities/planar';
16
+ import { isPointInsidePolyline3D } from '../../utilities/math/polyline';
17
+ import { triggerSegmentationDataModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
18
+ import { fillInsideCircle } from './strategies';
14
19
  export default class LabelmapBaseTool extends BaseTool {
20
+ static { this.previewData = {
21
+ preview: null,
22
+ element: null,
23
+ timerStart: 0,
24
+ timer: null,
25
+ startPoint: [NaN, NaN],
26
+ isDrag: false,
27
+ }; }
15
28
  constructor(toolProps, defaultToolProps) {
16
29
  super(toolProps, defaultToolProps);
17
- this._previewData = {
18
- preview: null,
19
- element: null,
20
- timerStart: 0,
21
- timer: null,
22
- startPoint: [NaN, NaN],
23
- isDrag: false,
24
- };
30
+ }
31
+ get _previewData() {
32
+ return LabelmapBaseTool.previewData;
25
33
  }
26
34
  createMemo(segmentId, segmentationVoxelManager, preview) {
27
35
  this.memo ||= LabelmapMemo.createLabelmapMemo(segmentId, segmentationVoxelManager, preview);
@@ -175,7 +183,7 @@ export default class LabelmapBaseTool extends BaseTool {
175
183
  points: data?.handles?.points,
176
184
  segmentIndex,
177
185
  previewColors: this.configuration.preview?.enabled || this._previewData.preview
178
- ? this.configuration.preview.previewColors
186
+ ? this.configuration.preview?.previewColors
179
187
  : null,
180
188
  viewPlaneNormal,
181
189
  toolGroupId: this.toolGroupId,
@@ -188,6 +196,7 @@ export default class LabelmapBaseTool extends BaseTool {
188
196
  return operationData;
189
197
  }
190
198
  addPreview(element = this._previewData.element, options) {
199
+ const { _previewData } = this;
191
200
  const acceptReject = options?.acceptReject;
192
201
  if (acceptReject === true) {
193
202
  this.acceptPreview(element);
@@ -196,9 +205,9 @@ export default class LabelmapBaseTool extends BaseTool {
196
205
  this.rejectPreview(element);
197
206
  }
198
207
  const enabledElement = getEnabledElement(element);
199
- this._previewData.preview = this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.AddPreview);
200
- this._previewData.isDrag = true;
201
- return this._previewData.preview;
208
+ _previewData.preview = this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.AddPreview);
209
+ _previewData.isDrag = true;
210
+ return _previewData.preview;
202
211
  }
203
212
  rejectPreview(element = this._previewData.element) {
204
213
  if (!element || !this._previewData.preview) {
@@ -220,4 +229,90 @@ export default class LabelmapBaseTool extends BaseTool {
220
229
  this._previewData.preview = null;
221
230
  this.doneEditMemo();
222
231
  }
232
+ static viewportContoursToLabelmap(viewport, options) {
233
+ const removeContours = options?.removeContours ?? true;
234
+ const annotations = getAllAnnotations();
235
+ const viewAnnotations = filterAnnotationsForDisplay(viewport, annotations);
236
+ if (!viewAnnotations?.length) {
237
+ return;
238
+ }
239
+ const contourAnnotations = viewAnnotations.filter((annotation) => annotation.data.contour?.polyline?.length);
240
+ if (!contourAnnotations.length) {
241
+ return;
242
+ }
243
+ const brushInstance = new LabelmapBaseTool({}, {
244
+ configuration: {
245
+ strategies: {
246
+ FILL_INSIDE_CIRCLE: fillInsideCircle,
247
+ },
248
+ activeStrategy: 'FILL_INSIDE_CIRCLE',
249
+ },
250
+ });
251
+ const preview = brushInstance.addPreview(viewport.element);
252
+ const { memo, segmentationId } = preview;
253
+ const previewVoxels = memo?.voxelManager || preview.previewVoxelManager;
254
+ const segmentationVoxels = previewVoxels.sourceVoxelManager || previewVoxels;
255
+ const { dimensions } = previewVoxels;
256
+ const imageData = viewport
257
+ .getDefaultActor()
258
+ .actor.getMapper()
259
+ .getInputData();
260
+ for (const annotation of contourAnnotations) {
261
+ const boundsIJK = [
262
+ [Infinity, -Infinity],
263
+ [Infinity, -Infinity],
264
+ [Infinity, -Infinity],
265
+ ];
266
+ const { polyline } = annotation.data.contour;
267
+ for (const point of polyline) {
268
+ const indexPoint = imageData.worldToIndex(point);
269
+ indexPoint.forEach((v, idx) => {
270
+ boundsIJK[idx][0] = Math.min(boundsIJK[idx][0], v);
271
+ boundsIJK[idx][1] = Math.max(boundsIJK[idx][1], v);
272
+ });
273
+ }
274
+ boundsIJK.forEach((bound, idx) => {
275
+ bound[0] = Math.round(Math.max(0, bound[0]));
276
+ bound[1] = Math.round(Math.min(dimensions[idx] - 1, bound[1]));
277
+ });
278
+ const activeIndex = getActiveSegmentIndex(segmentationId);
279
+ const startPoint = annotation.data.handles?.[0] || polyline[0];
280
+ const startIndex = imageData.worldToIndex(startPoint).map(Math.round);
281
+ const startValue = segmentationVoxels.getAtIJKPoint(startIndex) || 0;
282
+ let hasZeroIndex = false;
283
+ let hasPositiveIndex = false;
284
+ for (const polyPoint of polyline) {
285
+ const polyIndex = imageData.worldToIndex(polyPoint).map(Math.round);
286
+ const polyValue = segmentationVoxels.getAtIJKPoint(polyIndex);
287
+ if (polyValue === startValue) {
288
+ hasZeroIndex = true;
289
+ }
290
+ else if (polyValue >= 0) {
291
+ hasPositiveIndex = true;
292
+ }
293
+ }
294
+ const hasBoth = hasZeroIndex && hasPositiveIndex;
295
+ const segmentIndex = hasBoth
296
+ ? startValue
297
+ : startValue === 0
298
+ ? activeIndex
299
+ : 0;
300
+ for (let i = boundsIJK[0][0]; i <= boundsIJK[0][1]; i++) {
301
+ for (let j = boundsIJK[1][0]; j <= boundsIJK[1][1]; j++) {
302
+ for (let k = boundsIJK[2][0]; k <= boundsIJK[2][1]; k++) {
303
+ const worldPoint = imageData.indexToWorld([i, j, k]);
304
+ const isContained = isPointInsidePolyline3D(worldPoint, polyline);
305
+ if (isContained) {
306
+ previewVoxels.setAtIJK(i, j, k, segmentIndex);
307
+ }
308
+ }
309
+ }
310
+ }
311
+ if (removeContours) {
312
+ removeAnnotation(annotation.annotationUID);
313
+ }
314
+ }
315
+ const slices = previewVoxels.getArrayOfModifiedSlices();
316
+ triggerSegmentationDataModified(segmentationId, slices);
317
+ }
223
318
  }
@@ -7,7 +7,6 @@ import RepresentationTypes from '../../enums/SegmentationRepresentations';
7
7
  import { setActiveSegmentIndex } from '../../stateManagement/segmentation/segmentIndex';
8
8
  import { getHoveredContourSegmentationAnnotation, getSegmentIndexAtLabelmapBorder, getSegmentIndexAtWorldPoint, } from '../../utilities/segmentation';
9
9
  import { state } from '../../store/state';
10
- import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
11
10
  class SegmentSelectTool extends BaseTool {
12
11
  static { this.SelectMode = {
13
12
  Inside: 'Inside',
@@ -1,7 +1,7 @@
1
1
  import determineSegmentIndex from './determineSegmentIndex';
2
2
  import dynamicThreshold from './dynamicThreshold';
3
3
  import erase from './erase';
4
- import islandRemoval from './islandRemoval';
4
+ import islandRemoval from './islandRemovalComposition';
5
5
  import preview from './preview';
6
6
  import regionFill from './regionFill';
7
7
  import setValue from './setValue';
@@ -0,0 +1,5 @@
1
+ import type { InitializedOperationData } from '../BrushStrategy';
2
+ declare const _default: {
3
+ onInteractionEnd: (operationData: InitializedOperationData) => void;
4
+ };
5
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { triggerSegmentationDataModified } from '../../../../stateManagement/segmentation/triggerSegmentationEvents';
2
+ import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
3
+ import IslandRemoval from '../../../../utilities/segmentation/islandRemoval';
4
+ export default {
5
+ [StrategyCallbacks.OnInteractionEnd]: (operationData) => {
6
+ const { strategySpecificConfiguration, previewSegmentIndex, segmentIndex, viewport, previewVoxelManager, segmentationVoxelManager, } = operationData;
7
+ if (!strategySpecificConfiguration.THRESHOLD || segmentIndex === null) {
8
+ return;
9
+ }
10
+ const islandRemoval = new IslandRemoval();
11
+ const voxelManager = previewVoxelManager ?? segmentationVoxelManager;
12
+ if (!islandRemoval.initialize(viewport, voxelManager, {
13
+ previewSegmentIndex,
14
+ segmentIndex,
15
+ })) {
16
+ return;
17
+ }
18
+ islandRemoval.floodFillSegmentIsland();
19
+ islandRemoval.removeExternalIslands();
20
+ islandRemoval.removeInternalIslands();
21
+ const arrayOfSlices = voxelManager.getArrayOfModifiedSlices();
22
+ if (!arrayOfSlices) {
23
+ return;
24
+ }
25
+ triggerSegmentationDataModified(operationData.segmentationId, arrayOfSlices, previewSegmentIndex);
26
+ },
27
+ };
@@ -2,6 +2,11 @@ import StrategyCallbacks from '../../../../enums/StrategyCallbacks';
2
2
  import VolumetricCalculator from '../../../../utilities/segmentation/VolumetricCalculator';
3
3
  import { getActiveSegmentIndex } from '../../../../stateManagement/segmentation/getActiveSegmentIndex';
4
4
  import { getStrategyData } from '../utils/getStrategyData';
5
+ import { utilities } from '@cornerstonejs/core';
6
+ import { getPixelValueUnits } from '../../../../utilities/getPixelValueUnits';
7
+ import { AnnotationTool } from '../../../base';
8
+ import { isViewportPreScaled } from '../../../../utilities/viewport/isViewportPreScaled';
9
+ const radiusForVol1 = Math.pow((3 * 1000) / (4 * Math.PI), 1 / 3);
5
10
  export default {
6
11
  [StrategyCallbacks.GetStatistics]: function (enabledElement, operationData, options) {
7
12
  const { viewport } = enabledElement;
@@ -29,8 +34,70 @@ export default {
29
34
  return;
30
35
  }
31
36
  const imageValue = imageVoxelManager.getAtIJKPoint(pointIJK);
32
- VolumetricCalculator.statsCallback({ value: imageValue });
37
+ VolumetricCalculator.statsCallback({ value: imageValue, pointIJK });
33
38
  });
34
- return VolumetricCalculator.getStatistics({ spacing });
39
+ const targetId = viewport.getReferenceId();
40
+ const modalityUnitOptions = {
41
+ isPreScaled: isViewportPreScaled(viewport, targetId),
42
+ isSuvScaled: AnnotationTool.isSuvScaled(viewport, targetId, viewport.getCurrentImageId()),
43
+ };
44
+ const imageData = viewport.getImageData();
45
+ const unit = getPixelValueUnits(imageData.metadata.Modality, viewport.getCurrentImageId(), modalityUnitOptions);
46
+ const stats = VolumetricCalculator.getStatistics({ spacing, unit });
47
+ const { maxIJKs } = stats;
48
+ if (!maxIJKs?.length) {
49
+ return stats;
50
+ }
51
+ stats.mean.unit = unit;
52
+ stats.max.unit = unit;
53
+ stats.min.unit = unit;
54
+ if (unit !== 'SUV') {
55
+ return;
56
+ }
57
+ const radiusIJK = spacing.map((s) => Math.max(1, Math.round((1.1 * radiusForVol1) / s)));
58
+ for (const testMax of maxIJKs) {
59
+ const testStats = getSphereStats(testMax, radiusIJK, segmentationImageData, imageVoxelManager, spacing);
60
+ if (!testStats) {
61
+ continue;
62
+ }
63
+ const { mean } = testStats;
64
+ if (!stats.peakValue || stats.peakValue.value <= mean.value) {
65
+ stats.peakValue = {
66
+ name: 'peakValue',
67
+ label: 'Peak Value',
68
+ value: mean.value,
69
+ unit,
70
+ };
71
+ }
72
+ }
73
+ return stats;
35
74
  },
36
75
  };
76
+ function getSphereStats(testMax, radiusIJK, segData, imageVoxels, spacing) {
77
+ const { pointIJK: centerIJK } = testMax;
78
+ const boundsIJK = centerIJK.map((ijk, idx) => [
79
+ ijk - radiusIJK[idx],
80
+ ijk + radiusIJK[idx],
81
+ ]);
82
+ const testFunction = (_pointLPS, pointIJK) => {
83
+ const i = (pointIJK[0] - centerIJK[0]) / radiusIJK[0];
84
+ const j = (pointIJK[1] - centerIJK[1]) / radiusIJK[1];
85
+ const k = (pointIJK[2] - centerIJK[2]) / radiusIJK[2];
86
+ const radius = i * i + j * j + k * k;
87
+ return radius <= 1;
88
+ };
89
+ const statsFunction = ({ pointIJK, pointLPS }) => {
90
+ const value = imageVoxels.getAtIJKPoint(pointIJK);
91
+ if (value === undefined) {
92
+ return;
93
+ }
94
+ VolumetricCalculator.statsCallback({ value, pointLPS, pointIJK });
95
+ };
96
+ VolumetricCalculator.statsInit({ storePointData: false });
97
+ utilities.pointInShapeCallback(segData, {
98
+ pointInShapeFn: testFunction,
99
+ callback: statsFunction,
100
+ boundsIJK,
101
+ });
102
+ return VolumetricCalculator.getStatistics({ spacing });
103
+ }
@@ -31,6 +31,10 @@ type NamedStatistics = {
31
31
  name: 'circumference';
32
32
  };
33
33
  pointsInShape?: Types.IPointsManager<Types.Point3>;
34
+ maxIJKs?: Array<{
35
+ value: number;
36
+ pointIJK: Types.Point3;
37
+ }>;
34
38
  array: Statistics[];
35
39
  };
36
40
  export type { Statistics, NamedStatistics };
@@ -1,4 +1,4 @@
1
- import type { Types } from '@cornerstonejs/core';
1
+ import type { Enums as coreEnums, Types } from '@cornerstonejs/core';
2
2
  import type * as Enums from '../enums';
3
3
  import type { ContourSegmentationData } from './ContourTypes';
4
4
  import type { LabelmapSegmentationData } from './LabelmapTypes';
@@ -35,6 +35,7 @@ export type LabelmapRenderingConfig = {
35
35
  cfun: vtkColorTransferFunction;
36
36
  ofun: vtkPiecewiseFunction;
37
37
  colorLUTIndex: number;
38
+ blendMode?: coreEnums.BlendModes;
38
39
  };
39
40
  export type ContourRenderingConfig = {};
40
41
  export type SurfaceRenderingConfig = {};
@@ -89,6 +90,7 @@ export type RepresentationPublicInput = {
89
90
  type?: Enums.SegmentationRepresentations;
90
91
  config?: {
91
92
  colorLUTOrIndex?: Types.ColorLUT | number;
93
+ blendMode?: coreEnums.BlendModes;
92
94
  };
93
95
  };
94
96
  export {};
@@ -11,8 +11,11 @@ export function addContourSegmentationAnnotation(annotation) {
11
11
  if (!segmentation.representationData.Contour) {
12
12
  segmentation.representationData.Contour = { annotationUIDsMap: new Map() };
13
13
  }
14
- const { annotationUIDsMap } = segmentation.representationData.Contour;
15
- let annotationsUIDsSet = annotationUIDsMap.get(segmentIndex);
14
+ let { annotationUIDsMap } = segmentation.representationData.Contour;
15
+ if (!annotationUIDsMap) {
16
+ annotationUIDsMap = new Map();
17
+ }
18
+ let annotationsUIDsSet = annotationUIDsMap?.get(segmentIndex);
16
19
  if (!annotationsUIDsSet) {
17
20
  annotationsUIDsSet = new Set();
18
21
  annotationUIDsMap.set(segmentIndex, annotationsUIDsSet);
@@ -33,4 +33,6 @@ import * as voi from './voi';
33
33
  import * as contourSegmentation from './contourSegmentation';
34
34
  import { pointInSurroundingSphereCallback } from './pointInSurroundingSphereCallback';
35
35
  declare const roundNumber: typeof utilities.roundNumber;
36
- export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, };
36
+ import normalizeViewportPlane from './normalizeViewportPlane';
37
+ import IslandRemoval from './segmentation/islandRemoval';
38
+ export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, normalizeViewportPlane, IslandRemoval, };
@@ -33,4 +33,6 @@ import * as voi from './voi';
33
33
  import * as contourSegmentation from './contourSegmentation';
34
34
  import { pointInSurroundingSphereCallback } from './pointInSurroundingSphereCallback';
35
35
  const roundNumber = utilities.roundNumber;
36
- export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, };
36
+ import normalizeViewportPlane from './normalizeViewportPlane';
37
+ import IslandRemoval from './segmentation/islandRemoval';
38
+ export { math, planar, viewportFilters, drawing, debounce, dynamicVolume, throttle, orientation, isObject, touch, triggerEvent, calibrateImageSpacing, getCalibratedLengthUnitsAndScale, getCalibratedProbeUnitsAndValue, getCalibratedAspect, segmentation, contours, triggerAnnotationRenderForViewportIds, triggerAnnotationRenderForToolGroupIds, triggerAnnotationRender, getSphereBoundsInfo, getAnnotationNearPoint, getViewportForAnnotation, getAnnotationNearPointOnEnabledElement, viewport, cine, boundingBox, rectangleROITool, planarFreehandROITool, stackPrefetch, stackContextPrefetch, roundNumber, pointToString, polyDataUtils, voi, annotationFrameRange, contourSegmentation, annotationHydration, getClosestImageIdForStackViewport, pointInSurroundingSphereCallback, normalizeViewportPlane, IslandRemoval, };
@@ -27,14 +27,14 @@ export default function filterAnnotationsWithinSlice(annotations, camera, spacin
27
27
  const annotationsWithinSlice = [];
28
28
  for (const annotation of annotationsWithParallelNormals) {
29
29
  const data = annotation.data;
30
- const point = data.handles.points[0];
30
+ const point = data.handles.points[0] || data.contour?.polyline[0];
31
31
  if (!annotation.isVisible) {
32
32
  continue;
33
33
  }
34
34
  const dir = vec3.create();
35
35
  if (!point) {
36
36
  annotationsWithinSlice.push(annotation);
37
- return;
37
+ continue;
38
38
  }
39
39
  vec3.sub(dir, focalPoint, point);
40
40
  const dot = vec3.dot(dir, viewPlaneNormal);
@@ -1,2 +1,6 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
- export default function getPointInLineOfSightWithCriteria(viewport: Types.IVolumeViewport, worldPos: Types.Point3, targetVolumeId: string, criteriaFunction: (intensity: number, point: Types.Point3) => Types.Point3, stepSize?: number): Types.Point3;
2
+ export declare function getPointInLineOfSightWithCriteria(viewport: Types.IVolumeViewport, worldPos: Types.Point3, targetVolumeId: string, criteriaFunction: (intensity: number, point: Types.Point3) => Types.Point3, stepSize?: number): Types.Point3;
3
+ export declare function getPointsInLineOfSight(viewport: Types.IVolumeViewport, worldPos: Types.Point3, { targetVolumeId, stepSize }: {
4
+ targetVolumeId: string;
5
+ stepSize: number;
6
+ }): Types.Point3[];
@@ -1,38 +1,49 @@
1
- import vtkMath from '@kitware/vtk.js/Common/Core/Math';
2
1
  import { utilities as csUtils } from '@cornerstonejs/core';
3
- export default function getPointInLineOfSightWithCriteria(viewport, worldPos, targetVolumeId, criteriaFunction, stepSize = 0.25) {
4
- const camera = viewport.getCamera();
5
- const { position: cameraPosition } = camera;
6
- const { spacingInNormalDirection } = csUtils.getTargetVolumeAndSpacingInNormalDir(viewport, camera, targetVolumeId);
7
- const step = spacingInNormalDirection * stepSize;
8
- const bounds = viewport.getBounds();
9
- const xMin = bounds[0];
10
- const xMax = bounds[1];
11
- const vector = [0, 0, 0];
12
- let point = [0, 0, 0];
13
- vtkMath.subtract(worldPos, cameraPosition, vector);
2
+ export function getPointInLineOfSightWithCriteria(viewport, worldPos, targetVolumeId, criteriaFunction, stepSize = 0.25) {
3
+ const points = getPointsInLineOfSight(viewport, worldPos, {
4
+ targetVolumeId,
5
+ stepSize,
6
+ });
14
7
  let pickedPoint;
15
- for (let pointT = xMin; pointT <= xMax; pointT = pointT + step) {
16
- point = [pointT, 0, 0];
17
- const t = (pointT - cameraPosition[0]) / vector[0];
18
- point[1] = t * vector[1] + cameraPosition[1];
19
- point[2] = t * vector[2] + cameraPosition[2];
20
- if (_inBounds(point, bounds)) {
21
- const intensity = viewport.getIntensityFromWorld(point);
22
- const pointToPick = criteriaFunction(intensity, point);
23
- if (pointToPick) {
24
- pickedPoint = pointToPick;
25
- }
8
+ for (const point of points) {
9
+ const intensity = viewport.getIntensityFromWorld(point);
10
+ const pointToPick = criteriaFunction(intensity, point);
11
+ if (pointToPick) {
12
+ pickedPoint = pointToPick;
26
13
  }
27
14
  }
28
15
  return pickedPoint;
29
16
  }
17
+ export function getPointsInLineOfSight(viewport, worldPos, { targetVolumeId, stepSize }) {
18
+ const camera = viewport.getCamera();
19
+ const { viewPlaneNormal: normalDirection } = camera;
20
+ const { spacingInNormalDirection } = csUtils.getTargetVolumeAndSpacingInNormalDir(viewport, camera, targetVolumeId);
21
+ const step = spacingInNormalDirection * stepSize || 1;
22
+ const bounds = viewport.getBounds();
23
+ const points = [];
24
+ let currentPos = [...worldPos];
25
+ while (_inBounds(currentPos, bounds)) {
26
+ points.push([...currentPos]);
27
+ currentPos[0] += normalDirection[0] * step;
28
+ currentPos[1] += normalDirection[1] * step;
29
+ currentPos[2] += normalDirection[2] * step;
30
+ }
31
+ currentPos = [...worldPos];
32
+ while (_inBounds(currentPos, bounds)) {
33
+ points.push([...currentPos]);
34
+ currentPos[0] -= normalDirection[0] * step;
35
+ currentPos[1] -= normalDirection[1] * step;
36
+ currentPos[2] -= normalDirection[2] * step;
37
+ }
38
+ return points;
39
+ }
30
40
  const _inBounds = function (point, bounds) {
31
41
  const [xMin, xMax, yMin, yMax, zMin, zMax] = bounds;
32
- return (point[0] > xMin &&
33
- point[0] < xMax &&
34
- point[1] > yMin &&
35
- point[1] < yMax &&
36
- point[2] > zMin &&
37
- point[2] < zMax);
42
+ const padding = 10;
43
+ return (point[0] > xMin + padding &&
44
+ point[0] < xMax - padding &&
45
+ point[1] > yMin + padding &&
46
+ point[1] < yMax - padding &&
47
+ point[2] > zMin + padding &&
48
+ point[2] < zMax - padding);
38
49
  };
@@ -1,7 +1,7 @@
1
1
  import filterAnnotationsWithinSlice from './filterAnnotationsWithinSlice';
2
2
  import getWorldWidthAndHeightFromCorners from './getWorldWidthAndHeightFromCorners';
3
3
  import filterAnnotationsForDisplay from './filterAnnotationsForDisplay';
4
- import getPointInLineOfSightWithCriteria from './getPointInLineOfSightWithCriteria';
4
+ import { getPointInLineOfSightWithCriteria, getPointsInLineOfSight } from './getPointInLineOfSightWithCriteria';
5
5
  import { isPlaneIntersectingAABB } from './isPlaneIntersectingAABB';
6
6
  import { filterAnnotationsWithinSamePlane } from './filterAnnotationsWithinPlane';
7
7
  declare const _default: {
@@ -11,6 +11,7 @@ declare const _default: {
11
11
  getPointInLineOfSightWithCriteria: typeof getPointInLineOfSightWithCriteria;
12
12
  isPlaneIntersectingAABB: (origin: any, normal: any, minX: any, minY: any, minZ: any, maxX: any, maxY: any, maxZ: any) => boolean;
13
13
  filterAnnotationsWithinSamePlane: typeof filterAnnotationsWithinSamePlane;
14
+ getPointsInLineOfSight: typeof getPointsInLineOfSight;
14
15
  };
15
16
  export default _default;
16
- export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, };
17
+ export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, getPointsInLineOfSight, };
@@ -1,7 +1,7 @@
1
1
  import filterAnnotationsWithinSlice from './filterAnnotationsWithinSlice';
2
2
  import getWorldWidthAndHeightFromCorners from './getWorldWidthAndHeightFromCorners';
3
3
  import filterAnnotationsForDisplay from './filterAnnotationsForDisplay';
4
- import getPointInLineOfSightWithCriteria from './getPointInLineOfSightWithCriteria';
4
+ import { getPointInLineOfSightWithCriteria, getPointsInLineOfSight, } from './getPointInLineOfSightWithCriteria';
5
5
  import { isPlaneIntersectingAABB } from './isPlaneIntersectingAABB';
6
6
  import { filterAnnotationsWithinSamePlane } from './filterAnnotationsWithinPlane';
7
7
  export default {
@@ -11,5 +11,6 @@ export default {
11
11
  getPointInLineOfSightWithCriteria,
12
12
  isPlaneIntersectingAABB,
13
13
  filterAnnotationsWithinSamePlane,
14
+ getPointsInLineOfSight,
14
15
  };
15
- export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, };
16
+ export { filterAnnotationsWithinSlice, getWorldWidthAndHeightFromCorners, filterAnnotationsForDisplay, getPointInLineOfSightWithCriteria, isPlaneIntersectingAABB, filterAnnotationsWithinSamePlane, getPointsInLineOfSight, };
@@ -1,8 +1,15 @@
1
+ import type { Types } from '@cornerstonejs/core';
1
2
  import type { NamedStatistics } from '../../types';
2
3
  import { BasicStatsCalculator } from '../math/basic';
3
4
  export default class VolumetricCalculator extends BasicStatsCalculator {
5
+ private static maxIJKs;
4
6
  static getStatistics(options: {
5
7
  spacing?: number;
6
8
  unit?: string;
7
9
  }): NamedStatistics;
10
+ static statsCallback(data: {
11
+ value: number | Types.RGB;
12
+ pointLPS?: Types.Point3;
13
+ pointIJK?: Types.Point3;
14
+ }): void;
8
15
  }
@@ -1,5 +1,7 @@
1
1
  import { BasicStatsCalculator } from '../math/basic';
2
+ const TEST_MAX_LOCATIONS = 10;
2
3
  export default class VolumetricCalculator extends BasicStatsCalculator {
4
+ static { this.maxIJKs = []; }
3
5
  static getStatistics(options) {
4
6
  const { spacing } = options;
5
7
  const stats = BasicStatsCalculator.getStatistics();
@@ -14,7 +16,33 @@ export default class VolumetricCalculator extends BasicStatsCalculator {
14
16
  unit: volumeUnit,
15
17
  name: 'volume',
16
18
  };
19
+ stats.maxIJKs = this.maxIJKs;
17
20
  stats.array.push(stats.volume);
21
+ this.maxIJKs = [];
18
22
  return stats;
19
23
  }
24
+ static statsCallback(data) {
25
+ BasicStatsCalculator.statsCallback(data);
26
+ const { value } = data;
27
+ const { maxIJKs } = this;
28
+ const { length } = maxIJKs;
29
+ if (typeof value !== 'number' ||
30
+ (length >= TEST_MAX_LOCATIONS && value < maxIJKs[0].value)) {
31
+ return;
32
+ }
33
+ if (!length || value >= maxIJKs[length - 1].value) {
34
+ maxIJKs.push(data);
35
+ }
36
+ else {
37
+ for (let i = 0; i < length; i++) {
38
+ if (value <= maxIJKs[i].value) {
39
+ maxIJKs.splice(i, 0, data);
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ if (length >= TEST_MAX_LOCATIONS) {
45
+ maxIJKs.splice(0, 1);
46
+ }
47
+ }
20
48
  }
@@ -7,8 +7,6 @@ type BoundingBoxInfo = {
7
7
  };
8
8
  };
9
9
  type GrowCutBoundingBoxOptions = GrowCutOptions & {
10
- positiveSeedValue?: number;
11
- negativeSeedValue?: number;
12
10
  negativePixelRange?: [number, number];
13
11
  positivePixelRange?: [number, number];
14
12
  };
@@ -1,10 +1,6 @@
1
1
  import type { Types } from '@cornerstonejs/core';
2
2
  import type { GrowCutOptions } from './runGrowCut';
3
3
  type GrowCutOneClickOptions = GrowCutOptions & {
4
- positiveSeedValue?: number;
5
- negativeSeedValue?: number;
6
- positiveSeedVariance?: number;
7
- negativeSeedVariance?: number;
8
4
  subVolumePaddingPercentage?: number | [number, number, number];
9
5
  subVolumeMinPadding?: number | [number, number, number];
10
6
  };
@@ -19,4 +19,5 @@ import { getHoveredContourSegmentationAnnotation } from './getHoveredContourSegm
19
19
  import { getBrushToolInstances } from './getBrushToolInstances';
20
20
  import * as growCut from './growCut';
21
21
  import * as LabelmapMemo from './createLabelmapMemo';
22
- export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, };
22
+ import IslandRemoval from './islandRemoval';
23
+ export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, };
@@ -19,4 +19,5 @@ import { getHoveredContourSegmentationAnnotation } from './getHoveredContourSegm
19
19
  import { getBrushToolInstances } from './getBrushToolInstances';
20
20
  import * as growCut from './growCut';
21
21
  import * as LabelmapMemo from './createLabelmapMemo';
22
- export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, };
22
+ import IslandRemoval from './islandRemoval';
23
+ export { thresholdVolumeByRange, createMergedLabelmapForIndex, createLabelmapVolumeForViewport, rectangleROIThresholdVolumeByRange, triggerSegmentationRender, triggerSegmentationRenderBySegmentationId, floodFill, getBrushSizeForToolGroup, setBrushSizeForToolGroup, getBrushThresholdForToolGroup, setBrushThresholdForToolGroup, VolumetricCalculator, thresholdSegmentationByRange, contourAndFindLargestBidirectional, createBidirectionalToolData, segmentContourAction, invalidateBrushCursor, getUniqueSegmentIndices, getSegmentIndexAtWorldPoint, getSegmentIndexAtLabelmapBorder, getHoveredContourSegmentationAnnotation, getBrushToolInstances, growCut, LabelmapMemo, IslandRemoval, };
@@ -0,0 +1,28 @@
1
+ import type { Types } from '@cornerstonejs/core';
2
+ export declare enum SegmentationEnum {
3
+ SEGMENT = -1,
4
+ ISLAND = -2,
5
+ INTERIOR = -3,
6
+ EXTERIOR = -4,
7
+ INTERIOR_SMALL = -5,
8
+ INTERIOR_TEST = -6
9
+ }
10
+ export default class IslandRemoval {
11
+ segmentSet: Types.RLEVoxelMap<SegmentationEnum>;
12
+ segmentIndex: number;
13
+ fillSegments: (index: number) => boolean;
14
+ previewVoxelManager: Types.VoxelManager<number>;
15
+ previewSegmentIndex: number;
16
+ selectedPoints: Types.Point3[];
17
+ private fillInternalEdge;
18
+ private maxInternalRemove;
19
+ constructor(options?: {
20
+ maxInternalRemove?: number;
21
+ fillInternalEdge?: boolean;
22
+ });
23
+ initialize(viewport: any, segmentationVoxels: any, options: any): boolean;
24
+ floodFillSegmentIsland(): number;
25
+ removeExternalIslands(): void;
26
+ removeInternalIslands(): number[];
27
+ static covers(rle: any, row: any): boolean;
28
+ }