@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.
- package/LICENSE +21 -0
- package/dist/esm/enums/Events.d.ts +2 -0
- package/dist/esm/enums/Events.js +2 -0
- package/dist/esm/eventListeners/segmentation/imageChangeEventListener.js +5 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/stateManagement/annotation/annotationVisibility.js +2 -0
- package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.d.ts +1 -0
- package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.js +11 -0
- package/dist/esm/stateManagement/segmentation/config/segmentationVisibility.js +3 -0
- package/dist/esm/stateManagement/segmentation/helpers/normalizeSegmentationInput.js +13 -0
- package/dist/esm/stateManagement/segmentation/internalAddSegmentationRepresentation.js +11 -0
- package/dist/esm/store/ToolGroupManager/ToolGroup.js +10 -11
- package/dist/esm/tools/CrosshairsTool.d.ts +13 -12
- package/dist/esm/tools/CrosshairsTool.js +10 -3
- package/dist/esm/tools/OrientationMarkerTool.js +4 -2
- package/dist/esm/tools/OverlayGridTool.d.ts +2 -2
- package/dist/esm/tools/SegmentationIntersectionTool.d.ts +8 -5
- package/dist/esm/tools/SegmentationIntersectionTool.js +1 -1
- package/dist/esm/tools/VolumeCroppingControlTool.d.ts +91 -0
- package/dist/esm/tools/VolumeCroppingControlTool.js +1208 -0
- package/dist/esm/tools/VolumeCroppingTool.d.ts +96 -0
- package/dist/esm/tools/VolumeCroppingTool.js +1065 -0
- package/dist/esm/tools/WindowLevelTool.js +1 -1
- package/dist/esm/tools/annotation/ProbeTool.js +1 -0
- package/dist/esm/tools/annotation/UltrasoundPleuraBLineTool/utils/generateConvexHullFromContour.js +3 -3
- package/dist/esm/tools/annotation/VideoRedactionTool.d.ts +1 -1
- package/dist/esm/tools/annotation/VideoRedactionTool.js +1 -1
- package/dist/esm/tools/base/AnnotationTool.d.ts +1 -11
- package/dist/esm/tools/base/BaseTool.d.ts +2 -0
- package/dist/esm/tools/base/BaseTool.js +6 -0
- package/dist/esm/tools/index.d.ts +3 -1
- package/dist/esm/tools/index.js +3 -1
- package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +30 -16
- package/dist/esm/tools/segmentation/LabelmapBaseTool.d.ts +2 -0
- package/dist/esm/tools/segmentation/LabelmapBaseTool.js +11 -0
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +9 -3
- package/dist/esm/tools/segmentation/SegmentLabelTool.js +17 -5
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.d.ts +1 -0
- package/dist/esm/tools/segmentation/strategies/compositions/preview.js +2 -0
- package/dist/esm/tools/segmentation/strategies/fillCircle.d.ts +3 -6
- package/dist/esm/tools/segmentation/strategies/fillCircle.js +53 -30
- package/dist/esm/tools/segmentation/strategies/fillRectangle.js +26 -24
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +14 -12
- package/dist/esm/types/AnnotationTypes.d.ts +22 -19
- package/dist/esm/types/ContourAnnotation.d.ts +1 -0
- package/dist/esm/types/index.d.ts +2 -2
- package/dist/esm/utilities/planar/filterAnnotationsWithinSlice.js +17 -5
- package/dist/esm/utilities/segmentation/getBrushToolInstances.js +2 -2
- package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.d.ts +1 -1
- package/dist/esm/utilities/stackPrefetch/stackContextPrefetch.js +20 -10
- package/dist/esm/utilities/stackPrefetch/stackPrefetch.js +12 -2
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- 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);
|
package/dist/esm/tools/annotation/UltrasoundPleuraBLineTool/utils/generateConvexHullFromContour.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as math from '../../../../utilities/math';
|
|
2
2
|
export function generateConvexHullFromContour(contour) {
|
|
3
|
-
const simplified =
|
|
4
|
-
const hull =
|
|
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):
|
|
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.
|
|
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?: {
|
|
@@ -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, };
|
package/dist/esm/tools/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
223
|
-
|
|
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
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
endWorld
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
198
|
-
|
|
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 {
|
|
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
|
-
|
|
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,
|
|
119
|
+
const { hoveredSegmentIndex, hoveredSegmentLabel, canvasCoordinates, color, } = this._editData;
|
|
115
120
|
if (!hoveredSegmentIndex) {
|
|
116
121
|
return;
|
|
117
122
|
}
|
|
118
|
-
const
|
|
119
|
-
const
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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.
|
|
17
|
-
points.
|
|
18
|
-
vec3.add(center,
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
25
|
-
const
|
|
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(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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,
|
|
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
|
|
40
|
-
const
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
16
|
-
points.
|
|
17
|
-
vec3.add(center,
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
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);
|