@cornerstonejs/tools 2.0.0-beta.23 → 2.0.0-beta.25
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/dist/esm/enums/SegmentationRepresentations.d.ts +3 -3
- package/dist/esm/enums/SegmentationRepresentations.js +3 -3
- package/dist/esm/eventDispatchers/mouseEventHandlers/mouseMove.js +4 -0
- package/dist/esm/eventListeners/segmentation/labelmap/onLabelmapSegmentationDataModified.js +27 -23
- package/dist/esm/eventListeners/segmentation/segmentationDataModifiedEventListener.js +2 -2
- package/dist/esm/stateManagement/index.d.ts +3 -2
- package/dist/esm/stateManagement/index.js +2 -2
- package/dist/esm/stateManagement/segmentation/SegmentationRenderingEngine.d.ts +1 -2
- package/dist/esm/stateManagement/segmentation/SegmentationStateManager.d.ts +9 -6
- package/dist/esm/stateManagement/segmentation/SegmentationStateManager.js +72 -24
- package/dist/esm/stateManagement/segmentation/addSegmentationRepresentations.d.ts +8 -3
- package/dist/esm/stateManagement/segmentation/addSegmentationRepresentations.js +14 -4
- package/dist/esm/stateManagement/segmentation/config/segmentationConfig.d.ts +8 -7
- package/dist/esm/stateManagement/segmentation/convertVolumeToStackSegmentation.js +1 -1
- package/dist/esm/stateManagement/segmentation/getGlobalConfig.d.ts +2 -2
- package/dist/esm/stateManagement/segmentation/getPerSegmentConfig.d.ts +2 -2
- package/dist/esm/stateManagement/segmentation/getStackSegmentationImageIdsForViewport.d.ts +1 -0
- package/dist/esm/stateManagement/segmentation/getStackSegmentationImageIdsForViewport.js +5 -0
- package/dist/esm/stateManagement/segmentation/helpers/updateStackSegmentationState.js +5 -5
- package/dist/esm/stateManagement/segmentation/index.d.ts +2 -2
- package/dist/esm/stateManagement/segmentation/index.js +2 -2
- package/dist/esm/stateManagement/segmentation/internalAddSegmentationRepresentation.d.ts +3 -0
- package/dist/esm/stateManagement/segmentation/{addSegmentationRepresentation.js → internalAddSegmentationRepresentation.js} +4 -3
- package/dist/esm/stateManagement/segmentation/polySeg/Contour/contourComputationStrategies.js +4 -4
- package/dist/esm/stateManagement/segmentation/polySeg/Labelmap/labelmapComputationStrategies.js +4 -4
- package/dist/esm/stateManagement/segmentation/polySeg/Surface/createAndCacheSurfacesFromRaw.js +1 -1
- package/dist/esm/stateManagement/segmentation/polySeg/Surface/surfaceComputationStrategies.js +6 -6
- package/dist/esm/stateManagement/segmentation/polySeg/Surface/updateSurfaceData.js +2 -2
- package/dist/esm/stateManagement/segmentation/segmentationState.d.ts +2 -2
- package/dist/esm/stateManagement/segmentation/segmentationState.js +2 -2
- package/dist/esm/stateManagement/segmentation/setGlobalConfig.d.ts +2 -2
- package/dist/esm/stateManagement/segmentation/setPerSegmentConfig.d.ts +2 -2
- package/dist/esm/store/ToolGroupManager/ToolGroup.d.ts +1 -1
- package/dist/esm/tools/AdvancedMagnifyTool.js +2 -2
- package/dist/esm/tools/ScaleOverlayTool.js +23 -20
- package/dist/esm/tools/annotation/LivewireContourTool.d.ts +5 -0
- package/dist/esm/tools/annotation/LivewireContourTool.js +156 -8
- package/dist/esm/tools/base/BaseTool.d.ts +1 -13
- package/dist/esm/tools/base/ContourSegmentationBaseTool.js +1 -1
- package/dist/esm/tools/displayTools/Contour/contourHandler/handleContourSegmentation.js +4 -4
- package/dist/esm/tools/displayTools/Contour/contourHandler/utils.js +1 -1
- package/dist/esm/tools/displayTools/Contour/removeContourFromElement.js +1 -1
- package/dist/esm/tools/segmentation/BrushTool.d.ts +38 -0
- package/dist/esm/tools/segmentation/BrushTool.js +40 -7
- package/dist/esm/tools/segmentation/CircleROIStartEndThresholdTool.js +23 -15
- package/dist/esm/tools/segmentation/RectangleROIStartEndThresholdTool.js +6 -0
- package/dist/esm/tools/segmentation/strategies/BrushStrategy.js +10 -5
- package/dist/esm/tools/segmentation/strategies/compositions/islandRemoval.js +1 -1
- package/dist/esm/tools/segmentation/strategies/compositions/preview.js +2 -2
- package/dist/esm/tools/segmentation/strategies/compositions/setValue.js +6 -4
- package/dist/esm/tools/segmentation/strategies/fillSphere.js +5 -14
- package/dist/esm/types/EventTypes.d.ts +1 -2
- package/dist/esm/types/IBaseTool.d.ts +2 -0
- package/dist/esm/types/IBaseTool.js +1 -0
- package/dist/esm/types/IToolGroup.d.ts +3 -63
- package/dist/esm/types/LabelmapToolOperationData.d.ts +5 -0
- package/dist/esm/types/SegmentationStateTypes.d.ts +22 -22
- package/dist/esm/types/index.d.ts +3 -2
- package/dist/esm/utilities/contourSegmentation/addContourSegmentationAnnotation.js +3 -3
- package/dist/esm/utilities/contourSegmentation/removeContourSegmentationAnnotation.js +1 -1
- package/dist/esm/utilities/contours/generateContourSetsFromLabelmap.js +7 -7
- package/dist/esm/utilities/segmentation/getHoveredContourSegmentationAnnotation.js +1 -1
- package/dist/esm/utilities/segmentation/getSegmentIndexAtLabelmapBorder.js +8 -4
- package/dist/esm/utilities/segmentation/getSegmentIndexAtWorldPoint.js +2 -2
- package/dist/esm/utilities/segmentation/getUniqueSegmentIndices.js +2 -2
- package/dist/esm/utilities/segmentation/isLineInSegment.js +3 -3
- package/dist/umd/index.js +1 -1
- package/dist/umd/index.js.map +1 -1
- package/package.json +16 -7
- package/dist/esm/stateManagement/segmentation/addSegmentationRepresentation.d.ts +0 -3
package/dist/esm/stateManagement/segmentation/polySeg/Surface/createAndCacheSurfacesFromRaw.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function createAndCacheSurfacesFromRaw(segmentationId, rawSurfacesD
|
|
|
29
29
|
const geometryId = closedSurface.id;
|
|
30
30
|
geometryIds.set(segmentIndex, geometryId);
|
|
31
31
|
return geometryLoader.createAndCacheGeometry(geometryId, {
|
|
32
|
-
type: Enums.GeometryType.
|
|
32
|
+
type: Enums.GeometryType.Surface,
|
|
33
33
|
geometryData: closedSurface,
|
|
34
34
|
});
|
|
35
35
|
});
|
package/dist/esm/stateManagement/segmentation/polySeg/Surface/surfaceComputationStrategies.js
CHANGED
|
@@ -12,13 +12,13 @@ export async function computeSurfaceData(segmentationId, options = {}) {
|
|
|
12
12
|
const segmentation = getSegmentation(segmentationId);
|
|
13
13
|
const representationData = segmentation.representationData;
|
|
14
14
|
try {
|
|
15
|
-
if (representationData.
|
|
15
|
+
if (representationData.Contour) {
|
|
16
16
|
rawSurfacesData = await computeSurfaceFromContourSegmentation(segmentationId, {
|
|
17
17
|
segmentIndices,
|
|
18
18
|
...options,
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
else if (representationData.
|
|
21
|
+
else if (representationData.Labelmap) {
|
|
22
22
|
rawSurfacesData = await computeSurfaceFromLabelmapSegmentation(segmentation.segmentationId, {
|
|
23
23
|
segmentIndices,
|
|
24
24
|
...options,
|
|
@@ -37,12 +37,12 @@ export async function computeSurfaceData(segmentationId, options = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
async function computeSurfaceFromLabelmapSegmentation(segmentationId, options = {}) {
|
|
39
39
|
const segmentation = getSegmentation(segmentationId);
|
|
40
|
-
if (!segmentation?.representationData?.
|
|
40
|
+
if (!segmentation?.representationData?.Labelmap) {
|
|
41
41
|
console.warn('Only support surface update from labelmaps');
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
const isVolume = isVolumeSegmentation(segmentation.representationData.
|
|
45
|
-
const labelmapRepresentationData = segmentation.representationData.
|
|
44
|
+
const isVolume = isVolumeSegmentation(segmentation.representationData.Labelmap);
|
|
45
|
+
const labelmapRepresentationData = segmentation.representationData.Labelmap;
|
|
46
46
|
const segmentIndices = options.segmentIndices || getUniqueSegmentIndices(segmentationId);
|
|
47
47
|
const promises = segmentIndices.map((index) => {
|
|
48
48
|
const surface = convertLabelmapToSurface(labelmapRepresentationData, index, isVolume);
|
|
@@ -65,7 +65,7 @@ async function computeSurfaceFromLabelmapSegmentation(segmentationId, options =
|
|
|
65
65
|
}
|
|
66
66
|
async function computeSurfaceFromContourSegmentation(segmentationId, options = {}) {
|
|
67
67
|
const segmentation = getSegmentation(segmentationId);
|
|
68
|
-
const contourRepresentationData = segmentation.representationData.
|
|
68
|
+
const contourRepresentationData = segmentation.representationData.Contour;
|
|
69
69
|
const segmentIndices = options.segmentIndices || getUniqueSegmentIndices(segmentationId);
|
|
70
70
|
const promises = segmentIndices.map(async (index) => {
|
|
71
71
|
const surface = await convertContourToSurface(contourRepresentationData, index);
|
|
@@ -13,7 +13,7 @@ export async function updateSurfaceData(segmentationId) {
|
|
|
13
13
|
const segmentation = getSegmentation(segmentationId);
|
|
14
14
|
const indices = getUniqueSegmentIndices(segmentationId);
|
|
15
15
|
if (!indices.length) {
|
|
16
|
-
const geometryIds = segmentation.representationData.
|
|
16
|
+
const geometryIds = segmentation.representationData.Surface.geometryIds;
|
|
17
17
|
geometryIds.forEach((geometryId) => {
|
|
18
18
|
const geometry = cache.getGeometry(geometryId);
|
|
19
19
|
const surface = geometry.data;
|
|
@@ -35,7 +35,7 @@ export async function updateSurfaceData(segmentationId) {
|
|
|
35
35
|
SegmentationRepresentations.Surface) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
segmentation.representationData.
|
|
38
|
+
segmentation.representationData.Surface.geometryIds.set(segmentIndex, geometryId);
|
|
39
39
|
return createAndCacheSurfacesFromRaw(segmentationId, [{ segmentIndex, data }], {
|
|
40
40
|
segmentationRepresentationUID: segmentationRepresentation.segmentationRepresentationUID,
|
|
41
41
|
});
|
|
@@ -12,7 +12,6 @@ import { setSegmentationRepresentationConfig } from './setSegmentationRepresenta
|
|
|
12
12
|
import { getPerSegmentConfig } from './getPerSegmentConfig';
|
|
13
13
|
import { setPerSegmentConfig } from './setPerSegmentConfig';
|
|
14
14
|
import { getSegmentationRepresentations } from './getSegmentationRepresentations';
|
|
15
|
-
import { addSegmentationRepresentation } from './addSegmentationRepresentation';
|
|
16
15
|
import { getSegmentationRepresentationViewportStates } from './getSegmentationRepresentationViewportStates';
|
|
17
16
|
import { addColorLUT } from './addColorLUT';
|
|
18
17
|
import { getColorLUT } from './getColorLUT';
|
|
@@ -26,4 +25,5 @@ import { getActiveSegmentationRepresentation } from './getActiveSegmentationRepr
|
|
|
26
25
|
import { setActiveSegmentationRepresentation } from './setActiveSegmentationRepresentation';
|
|
27
26
|
import { getCurrentLabelmapImageIdForViewport } from './getCurrentLabelmapImageIdForViewport';
|
|
28
27
|
import { updateLabelmapSegmentationImageReferences } from './updateLabelmapSegmentationImageReferences';
|
|
29
|
-
|
|
28
|
+
import { getStackSegmentationImageIdsForViewport } from './getStackSegmentationImageIdsForViewport';
|
|
29
|
+
export { getSegmentation, getSegmentations, addSegmentation, removeSegmentation, getAllSegmentationRepresentations, getSegmentationRepresentation, removeRepresentation, getGlobalConfig, setGlobalConfig, getSegmentationRepresentationConfig, setSegmentationRepresentationConfig, getPerSegmentConfig, setPerSegmentConfig, getSegmentationRepresentations, getSegmentationRepresentationViewportStates, addColorLUT, getColorLUT, getNextColorLUTIndex, removeColorLUT, getSegmentationRepresentationsForSegmentation, getSegmentationRepresentationVisibility, setSegmentationRepresentationVisibility, getViewportIdsWithSegmentation, getActiveSegmentationRepresentation, setActiveSegmentationRepresentation, getCurrentLabelmapImageIdForViewport, updateLabelmapSegmentationImageReferences, getStackSegmentationImageIdsForViewport, };
|
|
@@ -12,7 +12,6 @@ import { setSegmentationRepresentationConfig } from './setSegmentationRepresenta
|
|
|
12
12
|
import { getPerSegmentConfig } from './getPerSegmentConfig';
|
|
13
13
|
import { setPerSegmentConfig } from './setPerSegmentConfig';
|
|
14
14
|
import { getSegmentationRepresentations } from './getSegmentationRepresentations';
|
|
15
|
-
import { addSegmentationRepresentation } from './addSegmentationRepresentation';
|
|
16
15
|
import { getSegmentationRepresentationViewportStates } from './getSegmentationRepresentationViewportStates';
|
|
17
16
|
import { addColorLUT } from './addColorLUT';
|
|
18
17
|
import { getColorLUT } from './getColorLUT';
|
|
@@ -26,4 +25,5 @@ import { getActiveSegmentationRepresentation } from './getActiveSegmentationRepr
|
|
|
26
25
|
import { setActiveSegmentationRepresentation } from './setActiveSegmentationRepresentation';
|
|
27
26
|
import { getCurrentLabelmapImageIdForViewport } from './getCurrentLabelmapImageIdForViewport';
|
|
28
27
|
import { updateLabelmapSegmentationImageReferences } from './updateLabelmapSegmentationImageReferences';
|
|
29
|
-
|
|
28
|
+
import { getStackSegmentationImageIdsForViewport } from './getStackSegmentationImageIdsForViewport';
|
|
29
|
+
export { getSegmentation, getSegmentations, addSegmentation, removeSegmentation, getAllSegmentationRepresentations, getSegmentationRepresentation, removeRepresentation, getGlobalConfig, setGlobalConfig, getSegmentationRepresentationConfig, setSegmentationRepresentationConfig, getPerSegmentConfig, setPerSegmentConfig, getSegmentationRepresentations, getSegmentationRepresentationViewportStates, addColorLUT, getColorLUT, getNextColorLUTIndex, removeColorLUT, getSegmentationRepresentationsForSegmentation, getSegmentationRepresentationVisibility, setSegmentationRepresentationVisibility, getViewportIdsWithSegmentation, getActiveSegmentationRepresentation, setActiveSegmentationRepresentation, getCurrentLabelmapImageIdForViewport, updateLabelmapSegmentationImageReferences, getStackSegmentationImageIdsForViewport, };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function setGlobalConfig(config:
|
|
1
|
+
import type { GlobalConfig } from '../../types';
|
|
2
|
+
export declare function setGlobalConfig(config: GlobalConfig, suppressEvents?: boolean): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function setPerSegmentConfig(segmentationRepresentationUID: string, config:
|
|
1
|
+
import type { RepresentationConfig } from '../../types';
|
|
2
|
+
export declare function setPerSegmentConfig(segmentationRepresentationUID: string, config: RepresentationConfig, suppressEvents?: boolean): void;
|
|
@@ -2,7 +2,7 @@ import { MouseBindings, ToolModes } from '../../enums';
|
|
|
2
2
|
import type { Types } from '@cornerstonejs/core';
|
|
3
3
|
import type { IToolBinding, IToolGroup, SetToolBindingsType, ToolOptionsType, ToolConfiguration } from '../../types';
|
|
4
4
|
import { MouseCursor } from '../../cursors';
|
|
5
|
-
export default class ToolGroup
|
|
5
|
+
export default class ToolGroup {
|
|
6
6
|
id: string;
|
|
7
7
|
viewportsInfo: any[];
|
|
8
8
|
toolOptions: {};
|
|
@@ -562,7 +562,7 @@ class AdvancedMagnifyViewportManager {
|
|
|
562
562
|
const { viewportId: sourceViewportId } = evt.detail;
|
|
563
563
|
this._reset(sourceViewportId);
|
|
564
564
|
};
|
|
565
|
-
element.addEventListener(csEvents.
|
|
565
|
+
element.addEventListener(csEvents.VIEWPORT_NEW_IMAGE_SET, newStackHandler);
|
|
566
566
|
const newVolumeHandler = (evt) => {
|
|
567
567
|
const { viewportId: sourceViewportId } = evt.detail;
|
|
568
568
|
this._reset(sourceViewportId);
|
|
@@ -575,7 +575,7 @@ class AdvancedMagnifyViewportManager {
|
|
|
575
575
|
_removeSourceElementEventListener(element) {
|
|
576
576
|
element.removeEventListener(csEvents.STACK_NEW_IMAGE, this._newStackImageCallback);
|
|
577
577
|
element.removeEventListener(csEvents.VOLUME_NEW_IMAGE, this._newVolumeImageCallback);
|
|
578
|
-
element.removeEventListener(csEvents.
|
|
578
|
+
element.removeEventListener(csEvents.VIEWPORT_NEW_IMAGE_SET, element.newStackHandler);
|
|
579
579
|
element.removeEventListener(csEvents.VOLUME_VIEWPORT_NEW_VOLUME, element.newVolumeHandler);
|
|
580
580
|
delete element.newStackHandler;
|
|
581
581
|
delete element.newVolumeHandler;
|
|
@@ -44,27 +44,30 @@ class ScaleOverlayTool extends AnnotationDisplayTool {
|
|
|
44
44
|
if (annotations.length) {
|
|
45
45
|
annotation = annotations.filter((thisAnnotation) => thisAnnotation.data.viewportId == viewport.id)[0];
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
handles: {
|
|
58
|
-
points: viewportCanvasCornersInWorld,
|
|
47
|
+
enabledElements.forEach((element) => {
|
|
48
|
+
const { viewport } = element;
|
|
49
|
+
if (!viewportsWithAnnotations.includes(viewport.id)) {
|
|
50
|
+
const newAnnotation = {
|
|
51
|
+
metadata: {
|
|
52
|
+
toolName: this.getToolName(),
|
|
53
|
+
viewPlaneNormal: [...viewPlaneNormal],
|
|
54
|
+
viewUp: [...viewUp],
|
|
55
|
+
FrameOfReferenceUID,
|
|
56
|
+
referencedImageId: null,
|
|
59
57
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
data: {
|
|
59
|
+
handles: {
|
|
60
|
+
points: csUtils.getViewportImageCornersInWorld(viewport),
|
|
61
|
+
},
|
|
62
|
+
viewportId: viewport.id,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
viewportsWithAnnotations.push(viewport.id);
|
|
66
|
+
addAnnotation(newAnnotation, viewport.element);
|
|
67
|
+
annotation = newAnnotation;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (this.editData.annotation &&
|
|
68
71
|
this.editData.annotation.data.viewportId == viewport.id) {
|
|
69
72
|
this.editData.annotation.data.handles.points =
|
|
70
73
|
viewportCanvasCornersInWorld;
|
|
@@ -10,10 +10,12 @@ declare class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
10
10
|
static toolName: string;
|
|
11
11
|
protected scissors: LivewireScissors;
|
|
12
12
|
protected scissorsNext: LivewireScissors;
|
|
13
|
+
_throttledCalculateCachedStats: Function;
|
|
13
14
|
editData: {
|
|
14
15
|
annotation: LivewireContourAnnotation;
|
|
15
16
|
viewportIdsToRender: Array<string>;
|
|
16
17
|
handleIndex?: number;
|
|
18
|
+
movingTextBox?: boolean;
|
|
17
19
|
newAnnotation?: boolean;
|
|
18
20
|
hasMoved?: boolean;
|
|
19
21
|
lastCanvasPoint?: Types.Point2;
|
|
@@ -57,6 +59,9 @@ declare class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
57
59
|
annotationStyle: AnnotationStyle;
|
|
58
60
|
svgDrawingHelper: SVGDrawingHelper;
|
|
59
61
|
}): boolean;
|
|
62
|
+
private _calculateCachedStats;
|
|
63
|
+
private _renderStats;
|
|
64
|
+
triggerAnnotationModified: (annotation: LivewireContourAnnotation, enabledElement: Types.IEnabledElement, changeType?: ChangeTypes) => void;
|
|
60
65
|
protected updateAnnotation(livewirePath: LivewirePath): void;
|
|
61
66
|
}
|
|
62
67
|
export default LivewireContourTool;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { vec3 } from 'gl-matrix';
|
|
2
|
-
import { getEnabledElement, utilities as csUtils, VolumeViewport, } from '@cornerstonejs/core';
|
|
2
|
+
import { getEnabledElement, utilities as csUtils, VolumeViewport, utilities, triggerEvent, eventTarget, } from '@cornerstonejs/core';
|
|
3
3
|
import { removeAnnotation } from '../../stateManagement/annotation/annotationState';
|
|
4
|
-
import { drawHandles as drawHandlesSvg } from '../../drawingSvg';
|
|
4
|
+
import { drawHandles as drawHandlesSvg, drawLinkedTextBox as drawLinkedTextBoxSvg, } from '../../drawingSvg';
|
|
5
5
|
import { state } from '../../store/state';
|
|
6
6
|
import { Events, KeyboardBindings, ChangeTypes } from '../../enums';
|
|
7
7
|
import { resetElementCursor } from '../../cursors/elementCursor';
|
|
@@ -15,11 +15,15 @@ import { LivewireScissors } from '../../utilities/livewire/LivewireScissors';
|
|
|
15
15
|
import { LivewirePath } from '../../utilities/livewire/LiveWirePath';
|
|
16
16
|
import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters';
|
|
17
17
|
import ContourSegmentationBaseTool from '../base/ContourSegmentationBaseTool';
|
|
18
|
+
import { getTextBoxCoordsCanvas } from '../../utilities/drawing';
|
|
19
|
+
import { getCalibratedLengthUnitsAndScale, throttle } from '../../utilities';
|
|
18
20
|
const CLICK_CLOSE_CURVE_SQR_DIST = 10 ** 2;
|
|
19
21
|
class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
20
22
|
constructor(toolProps = {}, defaultToolProps = {
|
|
21
23
|
supportedInteractionTypes: ['Mouse', 'Touch'],
|
|
22
24
|
configuration: {
|
|
25
|
+
getTextLines: defaultGetTextLines,
|
|
26
|
+
calculateStats: true,
|
|
23
27
|
preventHandleOutsideImage: false,
|
|
24
28
|
contourHoleAdditionModifierKey: KeyboardBindings.Shift,
|
|
25
29
|
snapHandleNearby: 2,
|
|
@@ -70,6 +74,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
70
74
|
this.editData = {
|
|
71
75
|
annotation,
|
|
72
76
|
viewportIdsToRender,
|
|
77
|
+
movingTextBox: false,
|
|
73
78
|
};
|
|
74
79
|
const enabledElement = getEnabledElement(element);
|
|
75
80
|
const { renderingEngine } = enabledElement;
|
|
@@ -82,13 +87,21 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
82
87
|
const { element } = eventDetail;
|
|
83
88
|
const { data } = annotation;
|
|
84
89
|
annotation.highlighted = true;
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
let movingTextBox = false;
|
|
91
|
+
let handleIndex;
|
|
92
|
+
if (handle.worldPosition) {
|
|
93
|
+
movingTextBox = true;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const { points } = data.handles;
|
|
97
|
+
handleIndex = points.findIndex((p) => p === handle);
|
|
98
|
+
}
|
|
87
99
|
const viewportIdsToRender = getViewportIdsWithToolToRender(element, this.getToolName());
|
|
88
100
|
this.editData = {
|
|
89
101
|
annotation,
|
|
90
102
|
viewportIdsToRender,
|
|
91
103
|
handleIndex,
|
|
104
|
+
movingTextBox,
|
|
92
105
|
};
|
|
93
106
|
this._activateModify(element);
|
|
94
107
|
const enabledElement = getEnabledElement(element);
|
|
@@ -215,15 +228,27 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
215
228
|
this.isDrawing = true;
|
|
216
229
|
const eventDetail = evt.detail;
|
|
217
230
|
const { element } = eventDetail;
|
|
218
|
-
const { annotation, viewportIdsToRender, handleIndex } = this.editData;
|
|
219
|
-
|
|
220
|
-
|
|
231
|
+
const { annotation, viewportIdsToRender, handleIndex, movingTextBox } = this.editData;
|
|
232
|
+
const { data } = annotation;
|
|
233
|
+
if (movingTextBox) {
|
|
234
|
+
const { deltaPoints } = eventDetail;
|
|
235
|
+
const worldPosDelta = deltaPoints.world;
|
|
236
|
+
const { textBox } = data.handles;
|
|
237
|
+
const { worldPosition } = textBox;
|
|
238
|
+
worldPosition[0] += worldPosDelta[0];
|
|
239
|
+
worldPosition[1] += worldPosDelta[1];
|
|
240
|
+
worldPosition[2] += worldPosDelta[2];
|
|
241
|
+
textBox.hasMoved = true;
|
|
242
|
+
}
|
|
243
|
+
else if (handleIndex === undefined) {
|
|
244
|
+
console.warn('Drag annotation not implemented');
|
|
221
245
|
}
|
|
222
246
|
else {
|
|
223
247
|
const { currentPoints } = eventDetail;
|
|
224
248
|
const worldPos = currentPoints.world;
|
|
225
249
|
this.editHandle(worldPos, element, annotation, handleIndex);
|
|
226
250
|
}
|
|
251
|
+
this.editData.hasMoved = true;
|
|
227
252
|
const enabledElement = getEnabledElement(element);
|
|
228
253
|
const { renderingEngine } = enabledElement;
|
|
229
254
|
triggerAnnotationRenderForViewportIds(viewportIdsToRender);
|
|
@@ -279,6 +304,104 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
279
304
|
element.removeEventListener(Events.MOUSE_DOUBLE_CLICK, this._mouseDownCallback);
|
|
280
305
|
element.removeEventListener(Events.TOUCH_TAP, this._mouseDownCallback);
|
|
281
306
|
};
|
|
307
|
+
this._calculateCachedStats = (annotation, element) => {
|
|
308
|
+
if (!this.configuration.calculateStats) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const data = annotation.data;
|
|
312
|
+
if (!data.contour.closed) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const enabledElement = getEnabledElement(element);
|
|
316
|
+
const { viewport, renderingEngine } = enabledElement;
|
|
317
|
+
const { cachedStats } = data;
|
|
318
|
+
const { polyline: points } = data.contour;
|
|
319
|
+
const targetIds = Object.keys(cachedStats);
|
|
320
|
+
for (let i = 0; i < targetIds.length; i++) {
|
|
321
|
+
const targetId = targetIds[i];
|
|
322
|
+
const image = this.getTargetIdImage(targetId, renderingEngine);
|
|
323
|
+
if (!image) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const { metadata } = image;
|
|
327
|
+
const canvasCoordinates = points.map((p) => viewport.worldToCanvas(p));
|
|
328
|
+
const canvasPoint = canvasCoordinates[0];
|
|
329
|
+
const originalWorldPoint = viewport.canvasToWorld(canvasPoint);
|
|
330
|
+
const deltaXPoint = viewport.canvasToWorld([
|
|
331
|
+
canvasPoint[0] + 1,
|
|
332
|
+
canvasPoint[1],
|
|
333
|
+
]);
|
|
334
|
+
const deltaYPoint = viewport.canvasToWorld([
|
|
335
|
+
canvasPoint[0],
|
|
336
|
+
canvasPoint[1] + 1,
|
|
337
|
+
]);
|
|
338
|
+
const deltaInX = vec3.distance(originalWorldPoint, deltaXPoint);
|
|
339
|
+
const deltaInY = vec3.distance(originalWorldPoint, deltaYPoint);
|
|
340
|
+
const { imageData } = image;
|
|
341
|
+
const { scale, areaUnit } = getCalibratedLengthUnitsAndScale(image, () => {
|
|
342
|
+
const { maxX: canvasMaxX, maxY: canvasMaxY, minX: canvasMinX, minY: canvasMinY, } = math.polyline.getAABB(canvasCoordinates);
|
|
343
|
+
const topLeftBBWorld = viewport.canvasToWorld([
|
|
344
|
+
canvasMinX,
|
|
345
|
+
canvasMinY,
|
|
346
|
+
]);
|
|
347
|
+
const topLeftBBIndex = utilities.transformWorldToIndex(imageData, topLeftBBWorld);
|
|
348
|
+
const bottomRightBBWorld = viewport.canvasToWorld([
|
|
349
|
+
canvasMaxX,
|
|
350
|
+
canvasMaxY,
|
|
351
|
+
]);
|
|
352
|
+
const bottomRightBBIndex = utilities.transformWorldToIndex(imageData, bottomRightBBWorld);
|
|
353
|
+
return [topLeftBBIndex, bottomRightBBIndex];
|
|
354
|
+
});
|
|
355
|
+
let area = math.polyline.getArea(canvasCoordinates) / scale / scale;
|
|
356
|
+
area *= deltaInX * deltaInY;
|
|
357
|
+
cachedStats[targetId] = {
|
|
358
|
+
Modality: metadata.Modality,
|
|
359
|
+
area,
|
|
360
|
+
areaUnit: areaUnit,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
this.triggerAnnotationModified(annotation, enabledElement, ChangeTypes.StatsUpdated);
|
|
364
|
+
return cachedStats;
|
|
365
|
+
};
|
|
366
|
+
this._renderStats = (annotation, viewport, svgDrawingHelper, textboxStyle) => {
|
|
367
|
+
const data = annotation.data;
|
|
368
|
+
const targetId = this.getTargetId(viewport);
|
|
369
|
+
if (!data.contour.closed || !textboxStyle.visibility) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const textLines = this.configuration.getTextLines(data, targetId);
|
|
373
|
+
if (!textLines || textLines.length === 0) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const canvasCoordinates = data.handles.points.map((p) => viewport.worldToCanvas(p));
|
|
377
|
+
if (!data.handles.textBox.hasMoved) {
|
|
378
|
+
const canvasTextBoxCoords = getTextBoxCoordsCanvas(canvasCoordinates);
|
|
379
|
+
data.handles.textBox.worldPosition =
|
|
380
|
+
viewport.canvasToWorld(canvasTextBoxCoords);
|
|
381
|
+
}
|
|
382
|
+
const textBoxPosition = viewport.worldToCanvas(data.handles.textBox.worldPosition);
|
|
383
|
+
const textBoxUID = 'textBox';
|
|
384
|
+
const boundingBox = drawLinkedTextBoxSvg(svgDrawingHelper, annotation.annotationUID ?? '', textBoxUID, textLines, textBoxPosition, canvasCoordinates, {}, textboxStyle);
|
|
385
|
+
const { x: left, y: top, width, height } = boundingBox;
|
|
386
|
+
data.handles.textBox.worldBoundingBox = {
|
|
387
|
+
topLeft: viewport.canvasToWorld([left, top]),
|
|
388
|
+
topRight: viewport.canvasToWorld([left + width, top]),
|
|
389
|
+
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
|
390
|
+
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
this.triggerAnnotationModified = (annotation, enabledElement, changeType = ChangeTypes.StatsUpdated) => {
|
|
394
|
+
const { viewportId, renderingEngineId } = enabledElement;
|
|
395
|
+
const eventType = Events.ANNOTATION_MODIFIED;
|
|
396
|
+
const eventDetail = {
|
|
397
|
+
annotation,
|
|
398
|
+
viewportId,
|
|
399
|
+
renderingEngineId,
|
|
400
|
+
changeType,
|
|
401
|
+
};
|
|
402
|
+
triggerEvent(eventTarget, eventType, eventDetail);
|
|
403
|
+
};
|
|
404
|
+
this._throttledCalculateCachedStats = throttle(this._calculateCachedStats, 100, { trailing: true });
|
|
282
405
|
}
|
|
283
406
|
setupBaseEditData(worldPos, element, annotation, nextPos, contourHoleProcessingEnabled) {
|
|
284
407
|
const enabledElement = getEnabledElement(element);
|
|
@@ -432,6 +555,7 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
432
555
|
editData.currentPath = currentPath;
|
|
433
556
|
annotation.invalidated = true;
|
|
434
557
|
editData.hasMoved = true;
|
|
558
|
+
editData.closed = true;
|
|
435
559
|
}
|
|
436
560
|
renderAnnotation(enabledElement, svgDrawingHelper) {
|
|
437
561
|
this.updateAnnotation(this.editData?.currentPath);
|
|
@@ -459,8 +583,9 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
459
583
|
this._endCallback(evt, true);
|
|
460
584
|
}
|
|
461
585
|
renderAnnotationInstance(renderContext) {
|
|
462
|
-
const { annotation, enabledElement, svgDrawingHelper, annotationStyle } = renderContext;
|
|
586
|
+
const { annotation, enabledElement, svgDrawingHelper, annotationStyle, targetId, } = renderContext;
|
|
463
587
|
const { viewport } = enabledElement;
|
|
588
|
+
const { element } = viewport;
|
|
464
589
|
const { worldToCanvas } = viewport;
|
|
465
590
|
const { annotationUID, data, highlighted } = annotation;
|
|
466
591
|
const { handles } = data;
|
|
@@ -478,6 +603,19 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
478
603
|
});
|
|
479
604
|
}
|
|
480
605
|
super.renderAnnotationInstance(renderContext);
|
|
606
|
+
if (!data.cachedStats[targetId] ||
|
|
607
|
+
data.cachedStats[targetId].areaUnit == null) {
|
|
608
|
+
data.cachedStats[targetId] = {
|
|
609
|
+
Modality: null,
|
|
610
|
+
area: null,
|
|
611
|
+
areaUnit: null,
|
|
612
|
+
};
|
|
613
|
+
this._calculateCachedStats(annotation, element);
|
|
614
|
+
}
|
|
615
|
+
else if (annotation.invalidated) {
|
|
616
|
+
this._throttledCalculateCachedStats(annotation, element);
|
|
617
|
+
}
|
|
618
|
+
this._renderStats(annotation, viewport, svgDrawingHelper, annotationStyle.textbox);
|
|
481
619
|
return true;
|
|
482
620
|
}
|
|
483
621
|
updateAnnotation(livewirePath) {
|
|
@@ -502,3 +640,13 @@ class LivewireContourTool extends ContourSegmentationBaseTool {
|
|
|
502
640
|
}
|
|
503
641
|
LivewireContourTool.toolName = 'LivewireContour';
|
|
504
642
|
export default LivewireContourTool;
|
|
643
|
+
function defaultGetTextLines(data, targetId) {
|
|
644
|
+
const cachedVolumeStats = data.cachedStats[targetId];
|
|
645
|
+
const { area, areaUnit } = cachedVolumeStats;
|
|
646
|
+
const textLines = [];
|
|
647
|
+
if (area) {
|
|
648
|
+
const areaLine = `Area: ${csUtils.roundNumber(area)} ${areaUnit}`;
|
|
649
|
+
textLines.push(areaLine);
|
|
650
|
+
}
|
|
651
|
+
return textLines;
|
|
652
|
+
}
|
|
@@ -2,19 +2,7 @@ import type { Types } from '@cornerstonejs/core';
|
|
|
2
2
|
import ToolModes from '../../enums/ToolModes';
|
|
3
3
|
import type StrategyCallbacks from '../../enums/StrategyCallbacks';
|
|
4
4
|
import type { InteractionTypes, ToolProps, PublicToolProps } from '../../types';
|
|
5
|
-
|
|
6
|
-
toolGroupId: string;
|
|
7
|
-
supportedInteractionTypes: InteractionTypes[];
|
|
8
|
-
mode: ToolModes;
|
|
9
|
-
configuration: {
|
|
10
|
-
preventHandleOutsideImage?: boolean;
|
|
11
|
-
strategies?: Record<string, unknown>;
|
|
12
|
-
defaultStrategy?: string;
|
|
13
|
-
activeStrategy?: string;
|
|
14
|
-
strategyOptions?: Record<string, unknown>;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
declare abstract class BaseTool implements IBaseTool {
|
|
5
|
+
declare abstract class BaseTool {
|
|
18
6
|
static toolName: any;
|
|
19
7
|
supportedInteractionTypes: InteractionTypes[];
|
|
20
8
|
configuration: Record<string, any>;
|
|
@@ -128,7 +128,7 @@ class ContourSegmentationBaseTool extends ContourBaseTool {
|
|
|
128
128
|
const activeSegRep = getActiveSegmentationRepresentation(viewportId);
|
|
129
129
|
const isActive = activeSegRep.segmentationRepresentationUID ===
|
|
130
130
|
segmentationRepresentationUID;
|
|
131
|
-
const mergedConfig = Object.assign({}, globalConfig?.representations?.
|
|
131
|
+
const mergedConfig = Object.assign({}, globalConfig?.representations?.Contour ?? {}, segmentationRepresentationConfig ?? {}, segmentConfig ?? {});
|
|
132
132
|
let lineWidth = 1;
|
|
133
133
|
let lineDash = undefined;
|
|
134
134
|
let lineOpacity = 1;
|
|
@@ -15,8 +15,8 @@ function handleContourSegmentation(viewport, geometryIds, annotationUIDsMap, con
|
|
|
15
15
|
}
|
|
16
16
|
function updateContourSets(viewport, geometryIds, contourRepresentation) {
|
|
17
17
|
const { segmentationRepresentationUID, config } = contourRepresentation;
|
|
18
|
-
const baseConfig = config?.allSegments
|
|
19
|
-
const globalContourConfig = getGlobalConfig().representations.
|
|
18
|
+
const baseConfig = config?.allSegments;
|
|
19
|
+
const globalContourConfig = getGlobalConfig().representations.Contour;
|
|
20
20
|
const newContourConfig = utilities.deepMerge(globalContourConfig, baseConfig);
|
|
21
21
|
const cachedConfig = getConfigCache(segmentationRepresentationUID);
|
|
22
22
|
const newOutlineWithActive = newContourConfig.outlineWidthActive;
|
|
@@ -112,8 +112,8 @@ function addContourSetsToElement(viewport, geometryIds, contourRepresentation) {
|
|
|
112
112
|
segmentSpecificMap.set(segmentIndex, segmentSpecificConfig);
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
|
-
const baseConfig = contourRepresentation.config?.allSegments
|
|
116
|
-
const globalContourConfig = getGlobalConfig().representations.
|
|
115
|
+
const baseConfig = contourRepresentation.config?.allSegments;
|
|
116
|
+
const globalContourConfig = getGlobalConfig().representations.Contour;
|
|
117
117
|
const newContourConfig = utilities.deepMerge(globalContourConfig, baseConfig);
|
|
118
118
|
const outlineWidthActive = newContourConfig.outlineWidthActive;
|
|
119
119
|
const segmentsHidden = getHiddenSegmentIndices(viewport.id, segmentationRepresentationUID);
|
|
@@ -7,7 +7,7 @@ export function validateGeometry(geometry) {
|
|
|
7
7
|
throw new Error(`No contours found for geometryId ${geometry.id}`);
|
|
8
8
|
}
|
|
9
9
|
const geometryId = geometry.id;
|
|
10
|
-
if (geometry.type !== Enums.GeometryType.
|
|
10
|
+
if (geometry.type !== Enums.GeometryType.Contour) {
|
|
11
11
|
throw new Error(`Geometry type ${geometry.type} not supported for rendering.`);
|
|
12
12
|
}
|
|
13
13
|
if (!geometry.data) {
|
|
@@ -4,7 +4,7 @@ function removeContourFromElement(element, segmentationRepresentationUID, remove
|
|
|
4
4
|
const segmentationRepresentation = getSegmentationRepresentation(segmentationRepresentationUID);
|
|
5
5
|
const { segmentationId } = segmentationRepresentation;
|
|
6
6
|
const segmentation = getSegmentation(segmentationId);
|
|
7
|
-
const { annotationUIDsMap } = segmentation.representationData.
|
|
7
|
+
const { annotationUIDsMap } = segmentation.representationData.Contour;
|
|
8
8
|
annotationUIDsMap.forEach((annotationSet) => {
|
|
9
9
|
annotationSet.forEach((annotationUID) => {
|
|
10
10
|
removeAnnotation(annotationUID);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Types } from '@cornerstonejs/core';
|
|
2
2
|
import type { PublicToolProps, ToolProps, EventTypes, SVGDrawingHelper } from '../../types';
|
|
3
3
|
import { BaseTool } from '../base';
|
|
4
|
+
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
|
4
5
|
export type PreviewData = {
|
|
5
6
|
preview: unknown;
|
|
6
7
|
timer?: number;
|
|
@@ -24,11 +25,22 @@ declare class BrushTool extends BaseTool {
|
|
|
24
25
|
referencedVolumeId: any;
|
|
25
26
|
segmentsLocked: number[] | [];
|
|
26
27
|
imageId?: undefined;
|
|
28
|
+
override?: undefined;
|
|
27
29
|
} | {
|
|
28
30
|
imageId: string;
|
|
29
31
|
segmentsLocked: number[] | [];
|
|
30
32
|
volumeId?: undefined;
|
|
31
33
|
referencedVolumeId?: undefined;
|
|
34
|
+
override?: undefined;
|
|
35
|
+
} | {
|
|
36
|
+
imageId: string;
|
|
37
|
+
segmentsLocked: number[] | [];
|
|
38
|
+
override: {
|
|
39
|
+
voxelManager: Types.IVoxelManager<number> | Types.IVoxelManager<Types.RGB>;
|
|
40
|
+
imageData: vtkImageData;
|
|
41
|
+
};
|
|
42
|
+
volumeId?: undefined;
|
|
43
|
+
referencedVolumeId?: undefined;
|
|
32
44
|
};
|
|
33
45
|
preMouseDownCallback: (evt: EventTypes.MouseDownActivateEventType) => boolean;
|
|
34
46
|
mouseMoveCallback: (evt: EventTypes.InteractionEventType) => void;
|
|
@@ -48,8 +60,13 @@ declare class BrushTool extends BaseTool {
|
|
|
48
60
|
viewUp: any;
|
|
49
61
|
strategySpecificConfiguration: any;
|
|
50
62
|
preview: unknown;
|
|
63
|
+
override: {
|
|
64
|
+
voxelManager: Types.IVoxelManager<number>;
|
|
65
|
+
imageData: vtkImageData;
|
|
66
|
+
};
|
|
51
67
|
segmentsLocked: number[];
|
|
52
68
|
imageId?: string;
|
|
69
|
+
imageIds?: string[];
|
|
53
70
|
volumeId?: string;
|
|
54
71
|
referencedVolumeId?: string;
|
|
55
72
|
} | {
|
|
@@ -67,6 +84,23 @@ declare class BrushTool extends BaseTool {
|
|
|
67
84
|
referencedVolumeId: any;
|
|
68
85
|
segmentsLocked: number[] | [];
|
|
69
86
|
imageId?: undefined;
|
|
87
|
+
override?: undefined;
|
|
88
|
+
} | {
|
|
89
|
+
points: any;
|
|
90
|
+
segmentIndex: number;
|
|
91
|
+
previewColors: any;
|
|
92
|
+
viewPlaneNormal: any;
|
|
93
|
+
toolGroupId: string;
|
|
94
|
+
segmentationId: string;
|
|
95
|
+
segmentationRepresentationUID: string;
|
|
96
|
+
viewUp: any;
|
|
97
|
+
strategySpecificConfiguration: any;
|
|
98
|
+
preview: unknown;
|
|
99
|
+
imageId: string;
|
|
100
|
+
segmentsLocked: number[] | [];
|
|
101
|
+
volumeId?: undefined;
|
|
102
|
+
referencedVolumeId?: undefined;
|
|
103
|
+
override?: undefined;
|
|
70
104
|
} | {
|
|
71
105
|
points: any;
|
|
72
106
|
segmentIndex: number;
|
|
@@ -80,6 +114,10 @@ declare class BrushTool extends BaseTool {
|
|
|
80
114
|
preview: unknown;
|
|
81
115
|
imageId: string;
|
|
82
116
|
segmentsLocked: number[] | [];
|
|
117
|
+
override: {
|
|
118
|
+
voxelManager: Types.IVoxelManager<number> | Types.IVoxelManager<Types.RGB>;
|
|
119
|
+
imageData: vtkImageData;
|
|
120
|
+
};
|
|
83
121
|
volumeId?: undefined;
|
|
84
122
|
referencedVolumeId?: undefined;
|
|
85
123
|
};
|