From 67a0367651fec5cbcfccb71da1921e9787e1a9e1 Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 11:31:47 -0400 Subject: [PATCH 1/9] refactor(watchStore): export from utils --- src/vtk/ToolWidgetUtils/pointState.js | 18 +----------------- src/vtk/ToolWidgetUtils/utils.ts | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/vtk/ToolWidgetUtils/pointState.js b/src/vtk/ToolWidgetUtils/pointState.js index 9c7ced295..a52d1caf9 100644 --- a/src/vtk/ToolWidgetUtils/pointState.js +++ b/src/vtk/ToolWidgetUtils/pointState.js @@ -2,27 +2,11 @@ import macro from '@kitware/vtk.js/macros'; import vtkWidgetState from '@kitware/vtk.js/Widgets/Core/WidgetState'; import visibleMixin from '@kitware/vtk.js/Widgets/Core/StateBuilder/visibleMixin'; import scale1Mixin from '@kitware/vtk.js/Widgets/Core/StateBuilder/scale1Mixin'; +import { watchStore } from '@/src/vtk/ToolWidgetUtils/utils'; import { ANNOTATION_TOOL_HANDLE_RADIUS } from '@/src/constants'; const PIXEL_SIZE = ANNOTATION_TOOL_HANDLE_RADIUS * 2; -function watchStore(publicAPI, store, getter, cmp) { - let cached = getter(); - const unsubscribe = store.$subscribe(() => { - const val = getter(); - if (cmp ? cmp(cached, val) : cached !== val) { - cached = val; - publicAPI.modified(); - } - }); - - const originalDelete = publicAPI.delete; - publicAPI.delete = () => { - unsubscribe(); - originalDelete(); - }; -} - function _createPointState( publicAPI, model, diff --git a/src/vtk/ToolWidgetUtils/utils.ts b/src/vtk/ToolWidgetUtils/utils.ts index ebdd7b7d8..0928d7755 100644 --- a/src/vtk/ToolWidgetUtils/utils.ts +++ b/src/vtk/ToolWidgetUtils/utils.ts @@ -1,6 +1,30 @@ +import { Maybe } from '@/src/types'; import vtkAbstractWidget from '@kitware/vtk.js/Widgets/Core/AbstractWidget'; import vtkPlaneManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator'; import { vtkSubscription } from '@kitware/vtk.js/interfaces'; +import { Store } from 'pinia'; + +export function watchStore( + publicAPI: any, + store: Store, + getter: () => Maybe, + cmp: (a: Maybe, b: Maybe) => boolean +) { + let cached = getter(); + const unsubscribe = store.$subscribe(() => { + const val = getter(); + if (cmp ? cmp(cached, val) : cached !== val) { + cached = val; + publicAPI.modified(); + } + }); + + const originalDelete = publicAPI.delete; + publicAPI.delete = () => { + unsubscribe(); + originalDelete(); + }; +} export function watchState( publicAPI: any, From 27058140607144b6c26a38f8e568e7a635fbdb68 Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 11:42:57 -0400 Subject: [PATCH 2/9] refactor(tools): use useFrameOfReference --- src/components/tools/polygon/PolygonTool.vue | 26 ++++++--------- .../tools/rectangle/RectangleTool.vue | 26 +++++---------- src/components/tools/ruler/RulerTool.vue | 27 ++++------------ src/composables/useFrameOfReference.ts | 32 +++++++++++++++++++ 4 files changed, 56 insertions(+), 55 deletions(-) create mode 100644 src/composables/useFrameOfReference.ts diff --git a/src/components/tools/polygon/PolygonTool.vue b/src/components/tools/polygon/PolygonTool.vue index 89c8c4fd8..f64057ee2 100644 --- a/src/components/tools/polygon/PolygonTool.vue +++ b/src/components/tools/polygon/PolygonTool.vue @@ -32,15 +32,12 @@ import { watch, } from 'vue'; import { storeToRefs } from 'pinia'; -import { vec3 } from 'gl-matrix'; import { useCurrentImage } from '@/src/composables/useCurrentImage'; import { useToolStore } from '@/src/store/tools'; import { Tools } from '@/src/store/tools/types'; import { getLPSAxisFromDir } from '@/src/utils/lps'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; -import type { Vector3 } from '@kitware/vtk.js/types'; import { LPSAxisDir } from '@/src/types/lps'; -import { FrameOfReference } from '@/src/utils/frameOfReference'; import { usePolygonStore } from '@/src/store/tools/polygons'; import { PolygonID } from '@/src/types/polygon'; import { @@ -51,6 +48,7 @@ import { import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue'; import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue'; import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue'; +import { useFrameOfReference } from '@/src/composables/useFrameOfReference'; import PolygonWidget2D from './PolygonWidget2D.vue'; type ToolID = PolygonID; @@ -158,19 +156,12 @@ export default defineComponent({ // --- updating active tool frame --- // - const getCurrentFrameOfReference = (): FrameOfReference => { - const { lpsOrientation, indexToWorld } = currentImageMetadata.value; - const planeNormal = lpsOrientation[viewDirection.value] as Vector3; - const lpsIdx = lpsOrientation[viewAxis.value]; - const planeOrigin: Vector3 = [0, 0, 0]; - planeOrigin[lpsIdx] = currentSlice.value; - // convert index pt to world pt - vec3.transformMat4(planeOrigin, planeOrigin, indexToWorld); - return { - planeNormal, - planeOrigin, - }; - }; + const frameOfReference = useFrameOfReference( + viewDirection, + currentSlice, + currentImageMetadata + ); + // update active tool's frame + slice, since the // active tool is not finalized. watch( @@ -178,7 +169,7 @@ export default defineComponent({ ([slice, toolID]) => { if (!toolID) return; activeToolStore.updateTool(toolID, { - frameOfReference: getCurrentFrameOfReference(), + frameOfReference: frameOfReference.value, slice, }); }, @@ -213,3 +204,4 @@ export default defineComponent({ +@/src/composables/useFrameOfReference diff --git a/src/components/tools/rectangle/RectangleTool.vue b/src/components/tools/rectangle/RectangleTool.vue index ba5cc1103..a3460d5a7 100644 --- a/src/components/tools/rectangle/RectangleTool.vue +++ b/src/components/tools/rectangle/RectangleTool.vue @@ -32,15 +32,12 @@ import { watch, } from 'vue'; import { storeToRefs } from 'pinia'; -import { vec3 } from 'gl-matrix'; import { useCurrentImage } from '@/src/composables/useCurrentImage'; import { useToolStore } from '@/src/store/tools'; import { Tools } from '@/src/store/tools/types'; import { getLPSAxisFromDir } from '@/src/utils/lps'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; -import type { Vector3 } from '@kitware/vtk.js/types'; import { LPSAxisDir } from '@/src/types/lps'; -import { FrameOfReference } from '@/src/utils/frameOfReference'; import { useRectangleStore } from '@/src/store/tools/rectangles'; import { RectangleID } from '@/src/types/rectangle'; import { @@ -51,6 +48,7 @@ import { import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue'; import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue'; import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue'; +import { useFrameOfReference } from '@/src/composables/useFrameOfReference'; import RectangleWidget2D from './RectangleWidget2D.vue'; type ToolID = RectangleID; @@ -158,20 +156,12 @@ export default defineComponent({ // --- updating active tool frame --- // - // TODO useCurrentFrameOfReference(viewDirection) - const getCurrentFrameOfReference = (): FrameOfReference => { - const { lpsOrientation, indexToWorld } = currentImageMetadata.value; - const planeNormal = lpsOrientation[viewDirection.value] as Vector3; - const lpsIdx = lpsOrientation[viewAxis.value]; - const planeOrigin: Vector3 = [0, 0, 0]; - planeOrigin[lpsIdx] = currentSlice.value; - // convert index pt to world pt - vec3.transformMat4(planeOrigin, planeOrigin, indexToWorld); - return { - planeNormal, - planeOrigin, - }; - }; + const frameOfReference = useFrameOfReference( + viewDirection, + currentSlice, + currentImageMetadata + ); + // update active ruler's frame + slice, since the // active ruler is not finalized. watch( @@ -179,7 +169,7 @@ export default defineComponent({ ([slice, toolID]) => { if (!toolID) return; activeToolStore.updateTool(toolID, { - frameOfReference: getCurrentFrameOfReference(), + frameOfReference: frameOfReference.value, slice, }); }, diff --git a/src/components/tools/ruler/RulerTool.vue b/src/components/tools/ruler/RulerTool.vue index dddb1eeeb..55780dc21 100644 --- a/src/components/tools/ruler/RulerTool.vue +++ b/src/components/tools/ruler/RulerTool.vue @@ -38,11 +38,8 @@ import { useRulerStore } from '@/src/store/tools/rulers'; import { getLPSAxisFromDir } from '@/src/utils/lps'; import RulerWidget2D from '@/src/components/tools/ruler/RulerWidget2D.vue'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; -import type { Vector3 } from '@kitware/vtk.js/types'; import { LPSAxisDir } from '@/src/types/lps'; import { storeToRefs } from 'pinia'; -import { FrameOfReference } from '@/src/utils/frameOfReference'; -import { vec3 } from 'gl-matrix'; import { useContextMenu, useCurrentTools, @@ -51,6 +48,7 @@ import { import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue'; import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue'; import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue'; +import { useFrameOfReference } from '@/src/composables/useFrameOfReference'; export default defineComponent({ name: 'RulerTool', @@ -153,22 +151,11 @@ export default defineComponent({ // --- updating active ruler frame --- // - // TODO useCurrentFrameOfReference(viewDirection) - const getCurrentFrameOfReference = (): FrameOfReference => { - const { lpsOrientation, indexToWorld } = currentImageMetadata.value; - const planeNormal = lpsOrientation[viewDirection.value] as Vector3; - - const lpsIdx = lpsOrientation[viewAxis.value]; - const planeOrigin: Vector3 = [0, 0, 0]; - planeOrigin[lpsIdx] = currentSlice.value; - // convert index pt to world pt - vec3.transformMat4(planeOrigin, planeOrigin, indexToWorld); - - return { - planeNormal, - planeOrigin, - }; - }; + const frameOfReference = useFrameOfReference( + viewDirection, + currentSlice, + currentImageMetadata + ); // update active ruler's frame + slice, since the // active ruler is not finalized. @@ -177,7 +164,7 @@ export default defineComponent({ ([slice, rulerID]) => { if (!rulerID) return; rulerStore.updateRuler(rulerID, { - frameOfReference: getCurrentFrameOfReference(), + frameOfReference: frameOfReference.value, slice, }); }, diff --git a/src/composables/useFrameOfReference.ts b/src/composables/useFrameOfReference.ts new file mode 100644 index 000000000..e16237e46 --- /dev/null +++ b/src/composables/useFrameOfReference.ts @@ -0,0 +1,32 @@ +import { ImageMetadata } from '@/src/types/image'; +import { LPSAxisDir } from '@/src/types/lps'; +import { FrameOfReference } from '@/src/utils/frameOfReference'; +import { getLPSAxisFromDir } from '@/src/utils/lps'; +import { Vector3 } from '@kitware/vtk.js/types'; +import { vec3 } from 'gl-matrix'; +import { computed, unref } from 'vue'; +import type { ComputedRef, MaybeRef } from 'vue'; + +export function useFrameOfReference( + viewDirection: MaybeRef, + slice: MaybeRef, + imageMetadata: MaybeRef +): ComputedRef { + const viewAxis = computed(() => getLPSAxisFromDir(unref(viewDirection))); + + return computed(() => { + const { lpsOrientation, indexToWorld } = unref(imageMetadata); + const planeNormal = lpsOrientation[unref(viewDirection)] as Vector3; + + const lpsIdx = lpsOrientation[viewAxis.value]; + const planeOrigin: Vector3 = [0, 0, 0]; + planeOrigin[lpsIdx] = unref(slice); + // convert index pt to world pt + vec3.transformMat4(planeOrigin, planeOrigin, indexToWorld); + + return { + planeNormal, + planeOrigin, + }; + }); +} From 0a6b8334b3ee0edb88e3fb17d7ff2d39b79993f2 Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 12:16:14 -0400 Subject: [PATCH 3/9] feat(tools): usePlacingAnnotationTool composable --- src/components/tools/polygon/PolygonTool.vue | 93 ++++++------------- .../tools/rectangle/RectangleTool.vue | 93 ++++++------------- src/components/tools/ruler/RulerTool.vue | 91 ++++++------------ src/composables/annotationTool.ts | 48 +++++++++- 4 files changed, 123 insertions(+), 202 deletions(-) diff --git a/src/components/tools/polygon/PolygonTool.vue b/src/components/tools/polygon/PolygonTool.vue index f64057ee2..26255e88e 100644 --- a/src/components/tools/polygon/PolygonTool.vue +++ b/src/components/tools/polygon/PolygonTool.vue @@ -27,7 +27,6 @@ import { defineComponent, onUnmounted, PropType, - ref, toRefs, watch, } from 'vue'; @@ -39,11 +38,11 @@ import { getLPSAxisFromDir } from '@/src/utils/lps'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; import { LPSAxisDir } from '@/src/types/lps'; import { usePolygonStore } from '@/src/store/tools/polygons'; -import { PolygonID } from '@/src/types/polygon'; import { useContextMenu, useCurrentTools, useHover, + usePlacingAnnotationTool, } from '@/src/composables/annotationTool'; import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue'; import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue'; @@ -51,7 +50,6 @@ import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue'; import { useFrameOfReference } from '@/src/composables/useFrameOfReference'; import PolygonWidget2D from './PolygonWidget2D.vue'; -type ToolID = PolygonID; const useActiveToolStore = usePolygonStore; const toolType = Tools.Polygon; @@ -91,90 +89,51 @@ export default defineComponent({ const isToolActive = computed(() => toolStore.currentTool === toolType); const viewAxis = computed(() => getLPSAxisFromDir(viewDirection.value)); - const placingToolID = ref(null); - // --- active tool management --- // - watch( - placingToolID, - (id, prevId) => { - if (prevId != null) { - activeToolStore.updateTool(prevId, { placing: false }); - } - if (id != null) { - activeToolStore.updateTool(id, { placing: true }); - } - }, - { immediate: true } + const frameOfReference = useFrameOfReference( + viewDirection, + currentSlice, + currentImageMetadata + ); + + const placingTool = usePlacingAnnotationTool( + activeToolStore, + computed(() => { + if (!currentImageID.value) return {}; + return { + imageID: currentImageID.value, + frameOfReference: frameOfReference.value, + slice: currentSlice.value, + label: activeLabel.value, + ...(activeLabel.value && activeToolStore.labels[activeLabel.value]), + }; + }) ); watch( [isToolActive, currentImageID] as const, ([active, imageID]) => { - if (placingToolID.value != null) { - activeToolStore.removeTool(placingToolID.value); - placingToolID.value = null; - } + placingTool.remove(); if (active && imageID) { - placingToolID.value = activeToolStore.addTool({ - imageID, - placing: true, - }); - } - }, - { immediate: true } - ); - - watch( - [activeLabel, placingToolID], - ([label, placingTool]) => { - if (placingTool != null) { - activeToolStore.updateTool(placingTool, { - label, - ...(label && activeToolStore.labels[label]), - }); + placingTool.add(); } }, { immediate: true } ); onUnmounted(() => { - if (placingToolID.value != null) { - activeToolStore.removeTool(placingToolID.value); - placingToolID.value = null; - } + placingTool.remove(); }); const onToolPlaced = () => { if (currentImageID.value) { - placingToolID.value = activeToolStore.addTool({ - imageID: currentImageID.value, - placing: true, - }); + placingTool.commit(); + placingTool.add(); } }; - // --- updating active tool frame --- // - - const frameOfReference = useFrameOfReference( - viewDirection, - currentSlice, - currentImageMetadata - ); - - // update active tool's frame + slice, since the - // active tool is not finalized. - watch( - [currentSlice, placingToolID] as const, - ([slice, toolID]) => { - if (!toolID) return; - activeToolStore.updateTool(toolID, { - frameOfReference: frameOfReference.value, - slice, - }); - }, - { immediate: true } - ); + // --- // const { contextMenu, openContextMenu } = useContextMenu(); @@ -190,7 +149,7 @@ export default defineComponent({ return { tools: currentTools, - placingToolID, + placingToolID: placingTool.id, onToolPlaced, contextMenu, openContextMenu, diff --git a/src/components/tools/rectangle/RectangleTool.vue b/src/components/tools/rectangle/RectangleTool.vue index a3460d5a7..bbba4a1ef 100644 --- a/src/components/tools/rectangle/RectangleTool.vue +++ b/src/components/tools/rectangle/RectangleTool.vue @@ -27,7 +27,6 @@ import { defineComponent, onUnmounted, PropType, - ref, toRefs, watch, } from 'vue'; @@ -39,11 +38,11 @@ import { getLPSAxisFromDir } from '@/src/utils/lps'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; import { LPSAxisDir } from '@/src/types/lps'; import { useRectangleStore } from '@/src/store/tools/rectangles'; -import { RectangleID } from '@/src/types/rectangle'; import { useCurrentTools, useContextMenu, useHover, + usePlacingAnnotationTool, } from '@/src/composables/annotationTool'; import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue'; import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue'; @@ -51,7 +50,6 @@ import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue'; import { useFrameOfReference } from '@/src/composables/useFrameOfReference'; import RectangleWidget2D from './RectangleWidget2D.vue'; -type ToolID = RectangleID; const useActiveToolStore = useRectangleStore; const toolType = Tools.Rectangle; @@ -91,90 +89,51 @@ export default defineComponent({ const isToolActive = computed(() => toolStore.currentTool === toolType); const viewAxis = computed(() => getLPSAxisFromDir(viewDirection.value)); - const placingToolID = ref(null); - // --- active tool management --- // - watch( - placingToolID, - (id, prevId) => { - if (prevId != null) { - activeToolStore.updateTool(prevId, { placing: false }); - } - if (id != null) { - activeToolStore.updateTool(id, { placing: true }); - } - }, - { immediate: true } + const frameOfReference = useFrameOfReference( + viewDirection, + currentSlice, + currentImageMetadata + ); + + const placingTool = usePlacingAnnotationTool( + activeToolStore, + computed(() => { + if (!currentImageID.value) return {}; + return { + imageID: currentImageID.value, + frameOfReference: frameOfReference.value, + slice: currentSlice.value, + label: activeLabel.value, + ...(activeLabel.value && activeToolStore.labels[activeLabel.value]), + }; + }) ); watch( [isToolActive, currentImageID] as const, ([active, imageID]) => { - if (placingToolID.value != null) { - activeToolStore.removeTool(placingToolID.value); - placingToolID.value = null; - } + placingTool.remove(); if (active && imageID) { - placingToolID.value = activeToolStore.addTool({ - imageID, - placing: true, - }); - } - }, - { immediate: true } - ); - - watch( - [activeLabel, placingToolID], - ([label, placingTool]) => { - if (placingTool != null) { - activeToolStore.updateTool(placingTool, { - label, - ...(label && activeToolStore.labels[label]), - }); + placingTool.add(); } }, { immediate: true } ); onUnmounted(() => { - if (placingToolID.value != null) { - activeToolStore.removeTool(placingToolID.value); - placingToolID.value = null; - } + placingTool.remove(); }); const onToolPlaced = () => { if (currentImageID.value) { - placingToolID.value = activeToolStore.addTool({ - imageID: currentImageID.value, - placing: true, - }); + placingTool.commit(); + placingTool.add(); } }; - // --- updating active tool frame --- // - - const frameOfReference = useFrameOfReference( - viewDirection, - currentSlice, - currentImageMetadata - ); - - // update active ruler's frame + slice, since the - // active ruler is not finalized. - watch( - [currentSlice, placingToolID] as const, - ([slice, toolID]) => { - if (!toolID) return; - activeToolStore.updateTool(toolID, { - frameOfReference: frameOfReference.value, - slice, - }); - }, - { immediate: true } - ); + // --- // const { contextMenu, openContextMenu } = useContextMenu(); @@ -190,7 +149,7 @@ export default defineComponent({ return { tools: currentTools, - placingToolID, + placingToolID: placingTool.id, onToolPlaced, contextMenu, openContextMenu, diff --git a/src/components/tools/ruler/RulerTool.vue b/src/components/tools/ruler/RulerTool.vue index 55780dc21..7823c650f 100644 --- a/src/components/tools/ruler/RulerTool.vue +++ b/src/components/tools/ruler/RulerTool.vue @@ -27,7 +27,6 @@ import { defineComponent, onUnmounted, PropType, - ref, toRefs, watch, } from 'vue'; @@ -44,6 +43,7 @@ import { useContextMenu, useCurrentTools, useHover, + usePlacingAnnotationTool, } from '@/src/composables/annotationTool'; import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue'; import AnnotationInfo from '@/src/components/tools/AnnotationInfo.vue'; @@ -86,90 +86,51 @@ export default defineComponent({ const isToolActive = computed(() => toolStore.currentTool === Tools.Ruler); const viewAxis = computed(() => getLPSAxisFromDir(viewDirection.value)); - const placingRulerID = ref(null); - // --- active ruler management --- // - watch( - placingRulerID, - (id, prevId) => { - if (prevId != null) { - rulerStore.updateRuler(prevId, { placing: false }); - } - if (id != null) { - rulerStore.updateRuler(id, { placing: true }); - } - }, - { immediate: true } + const frameOfReference = useFrameOfReference( + viewDirection, + currentSlice, + currentImageMetadata + ); + + const placingTool = usePlacingAnnotationTool( + rulerStore, + computed(() => { + if (!currentImageID.value) return {}; + return { + imageID: currentImageID.value, + frameOfReference: frameOfReference.value, + slice: currentSlice.value, + label: activeLabel.value, + ...(activeLabel.value && rulerStore.labels[activeLabel.value]), + }; + }) ); watch( [isToolActive, currentImageID] as const, ([active, imageID]) => { - if (placingRulerID.value != null) { - rulerStore.removeRuler(placingRulerID.value); - placingRulerID.value = null; - } + placingTool.remove(); if (active && imageID) { - placingRulerID.value = rulerStore.addRuler({ - imageID, - placing: true, - }); - } - }, - { immediate: true } - ); - - watch( - [activeLabel, placingRulerID], - ([label, placingTool]) => { - if (placingTool != null) { - rulerStore.updateRuler(placingTool, { - label, - ...(label && rulerStore.labels[label]), - }); + placingTool.add(); } }, { immediate: true } ); onUnmounted(() => { - if (placingRulerID.value != null) { - rulerStore.removeRuler(placingRulerID.value); - placingRulerID.value = null; - } + placingTool.remove(); }); const onRulerPlaced = () => { if (currentImageID.value) { - placingRulerID.value = rulerStore.addRuler({ - imageID: currentImageID.value, - placing: true, - }); + placingTool.commit(); + placingTool.add(); } }; - // --- updating active ruler frame --- // - - const frameOfReference = useFrameOfReference( - viewDirection, - currentSlice, - currentImageMetadata - ); - - // update active ruler's frame + slice, since the - // active ruler is not finalized. - watch( - [currentSlice, placingRulerID] as const, - ([slice, rulerID]) => { - if (!rulerID) return; - rulerStore.updateRuler(rulerID, { - frameOfReference: frameOfReference.value, - slice, - }); - }, - { immediate: true } - ); + // --- // const { contextMenu, openContextMenu } = useContextMenu(); @@ -195,7 +156,7 @@ export default defineComponent({ return { rulers: currentRulers, - placingRulerID, + placingRulerID: placingTool.id, onRulerPlaced, contextMenu, openContextMenu, diff --git a/src/composables/annotationTool.ts b/src/composables/annotationTool.ts index 3d672844b..40d0dc0b2 100644 --- a/src/composables/annotationTool.ts +++ b/src/composables/annotationTool.ts @@ -1,9 +1,10 @@ -import { Ref, computed, ref, watch } from 'vue'; +import { Ref, UnwrapRef, computed, readonly, ref, watch } from 'vue'; import { Vector2 } from '@kitware/vtk.js/types'; import { useCurrentImage } from '@/src/composables/useCurrentImage'; import { frameOfReferenceToImageSliceAndAxis } from '@/src/utils/frameOfReference'; import { vtkAnnotationToolWidget } from '@/src/vtk/ToolWidgetUtils/utils'; import { onVTKEvent } from '@/src/composables/onVTKEvent'; +import { Maybe } from '@/src/types'; import { useToolStore } from '@/src/store/tools'; import { Tools } from '@/src/store/tools/types'; import { AnnotationToolStore } from '@/src/store/tools/useAnnotationTool'; @@ -16,7 +17,7 @@ const SHOW_OVERLAY_DELAY = 250; // milliseconds // does the tools's frame of reference match // the view's axis -const useDoesToolFrameMatchViewAxis = < +const doesToolFrameMatchViewAxis = < ToolID extends string, Tool extends AnnotationTool >( @@ -49,7 +50,7 @@ export const useCurrentTools = ( // current view axis and not hidden return ( tool.imageID === curImageID && - useDoesToolFrameMatchViewAxis(viewAxis, tool) && + doesToolFrameMatchViewAxis(viewAxis, tool) && !tool.hidden ); }); @@ -184,3 +185,44 @@ export const useHover = ( return { overlayInfo: noInfoWithoutSelect, onHover }; }; + +export const usePlacingAnnotationTool = ( + store: AnnotationToolStore, + metadata: Ref>> +) => { + const id = ref>(null); + + const commit = () => { + const id_ = id.value as Maybe; + if (!id_) return; + store.updateTool(id_, { placing: false }); + id.value = null; + }; + + const add = () => { + if (id.value) throw new Error('Placing tool already exists.'); + id.value = store.addTool({ + ...metadata.value, + placing: true, + }) as UnwrapRef; + }; + + const remove = () => { + const id_ = id.value as Maybe; + if (!id_) return; + store.removeTool(id_); + id.value = null; + }; + + watch(metadata, () => { + if (!id.value) return; + store.updateTool(id.value as ToolID, metadata.value); + }); + + return { + id: readonly(id), + commit, + add, + remove, + }; +}; From 47c04dca1998f48e2b6b155b66c8c124469c305e Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 12:34:57 -0400 Subject: [PATCH 4/9] feat(useAnnotationTool): add finishedTools --- src/components/MeasurementsToolList.vue | 6 ++---- src/store/tools/useAnnotationTool.ts | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/MeasurementsToolList.vue b/src/components/MeasurementsToolList.vue index b65197e81..f8556757e 100644 --- a/src/components/MeasurementsToolList.vue +++ b/src/components/MeasurementsToolList.vue @@ -24,10 +24,8 @@ const { currentImageID, currentImageMetadata } = useCurrentImage(); // Filter and add axis for specific annotation type const getTools = (toolStore: AnnotationToolStore) => { - const byID = toolStore.toolByID; - return toolStore.toolIDs - .map((id) => byID[id]) - .filter((tool) => !tool.placing && tool.imageID === currentImageID.value) + return toolStore.finishedTools + .filter((tool) => tool.imageID === currentImageID.value) .map((tool) => { const { axis } = frameOfReferenceToImageSliceAndAxis( tool.frameOfReference, diff --git a/src/store/tools/useAnnotationTool.ts b/src/store/tools/useAnnotationTool.ts index bd63865be..12aef44b2 100644 --- a/src/store/tools/useAnnotationTool.ts +++ b/src/store/tools/useAnnotationTool.ts @@ -56,6 +56,10 @@ export const useAnnotationTool = < return toolIDs.value.map((id) => byID[id]); }); + const finishedTools = computed(() => + tools.value.filter((tool) => !tool.placing) + ); + const labels = useLabels(newLabelDefault); labels.mergeLabels(initialLabels, false); @@ -197,6 +201,7 @@ export const useAnnotationTool = < toolIDs, toolByID, tools, + finishedTools, addTool, removeTool, updateTool, From c6759725d91c589ed7205a4b7c6f93fab46e7bb8 Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 12:50:51 -0400 Subject: [PATCH 5/9] refactor(tools): readable visibility handling --- .../tools/rectangle/RectangleWidget2D.vue | 36 +++++++----------- src/components/tools/ruler/RulerWidget2D.vue | 38 ++++++------------- 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/components/tools/rectangle/RectangleWidget2D.vue b/src/components/tools/rectangle/RectangleWidget2D.vue index 1fda66452..6092226bb 100644 --- a/src/components/tools/rectangle/RectangleWidget2D.vue +++ b/src/components/tools/rectangle/RectangleWidget2D.vue @@ -11,6 +11,7 @@ import { toRefs, watch, watchEffect, + reactive, } from 'vue'; import vtkPlaneManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator'; import { useCurrentImage } from '@/src/composables/useCurrentImage'; @@ -23,8 +24,6 @@ import vtkRectangleWidget, { InteractionState, } from '@/src/vtk/RectangleWidget'; import RectangleSVG2D from '@/src/components/tools/rectangle/RectangleSVG2D.vue'; -import { vtkRulerWidgetPointState } from '@/src/vtk/RulerWidget'; -import { watchOnce } from '@vueuse/core'; import { RectangleID } from '@/src/types/rectangle'; import { useRightClickContextMenu, @@ -34,7 +33,6 @@ import { const useStore = useRectangleStore; const vtkWidgetFactory = vtkRectangleWidget; type WidgetView = vtkRectangleViewWidget; -type vtkWidgetPointState = vtkRulerWidgetPointState; type ToolID = RectangleID; const SVG2DComponent = RectangleSVG2D; @@ -176,33 +174,25 @@ export default defineComponent({ }); // --- handle pick visibility --- // - const usePointVisibility = ( - pointState: Ref - ) => { - const visible = ref(false); - const updateVisibility = () => { - if (!pointState.value) return; - visible.value = pointState.value.getVisible(); - }; - onVTKEvent(pointState, 'onModified', () => updateVisibility()); - watchOnce(pointState, () => updateVisibility()); - return visible; - }; - const firstPointVisible = usePointVisibility( - computed(() => widget.value?.getWidgetState().getFirstPoint()) - ); - const secondPointVisible = usePointVisibility( - computed(() => widget.value?.getWidgetState().getSecondPoint()) - ); + const visibleStates = reactive({ + firstPoint: false, + secondPoint: false, + }); + + const widgetState = widgetFactory.getWidgetState(); + onVTKEvent(widgetFactory.getWidgetState(), 'onModified', () => { + visibleStates.firstPoint = widgetState.getFirstPoint().getVisible(); + visibleStates.secondPoint = widgetState.getSecondPoint().getVisible(); + }); return { tool, firstPoint: computed(() => { - return firstPointVisible.value ? tool.value.firstPoint : undefined; + return visibleStates.firstPoint ? tool.value.firstPoint : undefined; }), secondPoint: computed(() => { - return secondPointVisible.value ? tool.value.secondPoint : undefined; + return visibleStates.secondPoint ? tool.value.secondPoint : undefined; }), }; }, diff --git a/src/components/tools/ruler/RulerWidget2D.vue b/src/components/tools/ruler/RulerWidget2D.vue index 0dc542715..59e120feb 100644 --- a/src/components/tools/ruler/RulerWidget2D.vue +++ b/src/components/tools/ruler/RulerWidget2D.vue @@ -2,16 +2,15 @@ import vtkRulerWidget, { InteractionState, vtkRulerViewWidget, - vtkRulerWidgetPointState, } from '@/src/vtk/RulerWidget'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; import { + reactive, computed, defineComponent, onMounted, onUnmounted, PropType, - Ref, ref, toRefs, watch, @@ -24,7 +23,6 @@ import { LPSAxisDir } from '@/src/types/lps'; import { useRulerStore } from '@/src/store/tools/rulers'; import { onVTKEvent } from '@/src/composables/onVTKEvent'; import RulerSVG2D from '@/src/components/tools/ruler/RulerSVG2D.vue'; -import { watchOnce } from '@vueuse/core'; import { useRightClickContextMenu, useHoverEvent, @@ -167,36 +165,24 @@ export default defineComponent({ // --- handle pick visibility --- // - const usePointVisibility = ( - pointState: Ref - ) => { - const visible = ref(false); - const updateVisibility = () => { - if (!pointState.value) return; - visible.value = pointState.value.getVisible(); - }; - - onVTKEvent(pointState, 'onModified', () => updateVisibility()); - - watchOnce(pointState, () => updateVisibility()); - - return visible; - }; + const visibleStates = reactive({ + firstPoint: false, + secondPoint: false, + }); - const firstPointVisible = usePointVisibility( - computed(() => widget.value?.getWidgetState().getFirstPoint()) - ); - const secondPointVisible = usePointVisibility( - computed(() => widget.value?.getWidgetState().getSecondPoint()) - ); + const widgetState = widgetFactory.getWidgetState(); + onVTKEvent(widgetFactory.getWidgetState(), 'onModified', () => { + visibleStates.firstPoint = widgetState.getFirstPoint().getVisible(); + visibleStates.secondPoint = widgetState.getSecondPoint().getVisible(); + }); return { ruler, firstPoint: computed(() => { - return firstPointVisible.value ? ruler.value.firstPoint : undefined; + return visibleStates.firstPoint ? ruler.value.firstPoint : undefined; }), secondPoint: computed(() => { - return secondPointVisible.value ? ruler.value.secondPoint : undefined; + return visibleStates.secondPoint ? ruler.value.secondPoint : undefined; }), length: computed(() => rulerStore.lengthByID[ruler.value.id]), }; From 59a35253bccfc019999a542584a8c94849e71540 Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 12:55:36 -0400 Subject: [PATCH 6/9] fix(polygon): remove movePoint state This state should not be stored inside the tool. --- src/components/tools/polygon/PolygonSVG2D.vue | 3 ++- .../tools/polygon/PolygonWidget2D.vue | 26 ++++++++++++------- src/store/tools/polygons.ts | 1 - 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/components/tools/polygon/PolygonSVG2D.vue b/src/components/tools/polygon/PolygonSVG2D.vue index b2625321b..42bbc2939 100644 --- a/src/components/tools/polygon/PolygonSVG2D.vue +++ b/src/components/tools/polygon/PolygonSVG2D.vue @@ -37,6 +37,7 @@ import { watch, inject, } from 'vue'; +import { Maybe } from '@/src/types'; const POINT_RADIUS = ANNOTATION_TOOL_HANDLE_RADIUS; const FINISHABLE_POINT_RADIUS = POINT_RADIUS + 6; @@ -53,7 +54,7 @@ export default defineComponent({ required: true, }, movePoint: { - type: Array as unknown as PropType, + type: Array as unknown as PropType>, }, placing: { type: Boolean, diff --git a/src/components/tools/polygon/PolygonWidget2D.vue b/src/components/tools/polygon/PolygonWidget2D.vue index 2c25670a2..8010d9cd8 100644 --- a/src/components/tools/polygon/PolygonWidget2D.vue +++ b/src/components/tools/polygon/PolygonWidget2D.vue @@ -1,6 +1,7 @@ -@/src/composables/useFrameOfReference From 8c2af0003177e19ef46487658b1d80a78d34c4f7 Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 14:14:55 -0400 Subject: [PATCH 8/9] fix(RulerWidget2D): init visible states --- src/components/tools/ruler/RulerWidget2D.vue | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/tools/ruler/RulerWidget2D.vue b/src/components/tools/ruler/RulerWidget2D.vue index 59e120feb..cc64b1dea 100644 --- a/src/components/tools/ruler/RulerWidget2D.vue +++ b/src/components/tools/ruler/RulerWidget2D.vue @@ -2,6 +2,7 @@ import vtkRulerWidget, { InteractionState, vtkRulerViewWidget, + vtkRulerWidgetState, } from '@/src/vtk/RulerWidget'; import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager'; import { @@ -170,11 +171,16 @@ export default defineComponent({ secondPoint: false, }); - const widgetState = widgetFactory.getWidgetState(); - onVTKEvent(widgetFactory.getWidgetState(), 'onModified', () => { + const updateVisibleState = (widgetState: vtkRulerWidgetState) => { visibleStates.firstPoint = widgetState.getFirstPoint().getVisible(); visibleStates.secondPoint = widgetState.getSecondPoint().getVisible(); - }); + }; + + const widgetState = widgetFactory.getWidgetState(); + onVTKEvent(widgetFactory.getWidgetState(), 'onModified', () => + updateVisibleState(widgetState) + ); + updateVisibleState(widgetState); return { ruler, From 45be61a6a2a348cf2d3fbcec539450351d4ab862 Mon Sep 17 00:00:00 2001 From: Forrest Date: Fri, 15 Sep 2023 14:15:09 -0400 Subject: [PATCH 9/9] fix(polygon): drop movePoint --- src/types/polygon.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types/polygon.ts b/src/types/polygon.ts index 878c4464b..e0daad615 100644 --- a/src/types/polygon.ts +++ b/src/types/polygon.ts @@ -8,5 +8,4 @@ export type Polygon = { * Points is in image index space. */ points: Array; - movePoint: Vector3; } & AnnotationTool;