Skip to content

Commit

Permalink
feat(rectangle): separate out placing rectangle
Browse files Browse the repository at this point in the history
  • Loading branch information
floryst committed Sep 13, 2023
1 parent e3ab0aa commit ba3193b
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 201 deletions.
161 changes: 161 additions & 0 deletions src/components/tools/rectangle/PlacingRectangleWidget2D.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<script lang="ts">
import vtkWidgetManager from '@kitware/vtk.js/Widgets/Core/WidgetManager';
import {
computed,
defineComponent,
onMounted,
onUnmounted,
PropType,
toRefs,
watch,
watchEffect,
} from 'vue';
import vtkPlaneManipulator from '@kitware/vtk.js/Widgets/Manipulators/PlaneManipulator';
import { useCurrentImage } from '@/src/composables/useCurrentImage';
import { updatePlaneManipulatorFor2DView } from '@/src/utils/manipulators';
import { LPSAxisDir } from '@/src/types/lps';
import { onVTKEvent } from '@/src/composables/onVTKEvent';
import vtkRectangleWidget, {
vtkRectangleViewWidget,
InteractionState,
vtkRectangleWidgetState,
} from '@/src/vtk/RectangleWidget';
import RectangleSVG2D from '@/src/components/tools/rectangle/RectangleSVG2D.vue';
// the rectangle widget is the same as the ruler widget
import createStandaloneState from '@/src/vtk/RulerWidget/standaloneState';
import { useSyncedRectangleState } from '@/src/components/tools/rectangle/common';
import { useViewWidget } from '@/src/composables/useViewWidget';
import type { RectangleInitState } from '@/src/components/tools/rectangle/common';
const vtkWidgetFactory = vtkRectangleWidget;
type WidgetView = vtkRectangleViewWidget;
const SVG2DComponent = RectangleSVG2D;
export default defineComponent({
name: 'RectangleWidget2D',
emits: ['placed'],
props: {
widgetManager: {
type: Object as PropType<vtkWidgetManager>,
required: true,
},
viewId: {
type: String,
required: true,
},
viewDirection: {
type: String as PropType<LPSAxisDir>,
required: true,
},
currentSlice: {
type: Number,
required: true,
},
color: String,
fillColor: String,
},
components: {
SVG2DComponent,
},
setup(props, { emit }) {
const { widgetManager, viewDirection, currentSlice } = toRefs(props);
const { currentImageID, currentImageMetadata } = useCurrentImage();
const widgetState = createStandaloneState() as vtkRectangleWidgetState;
const widgetFactory = vtkWidgetFactory.newInstance({
widgetState,
});
const syncedState = useSyncedRectangleState(widgetFactory);
const widget = useViewWidget<WidgetView>(widgetFactory, widgetManager);
onMounted(() => {
widget.value!.setInteractionState(InteractionState.PlacingFirst);
});
onUnmounted(() => {
widgetFactory.delete();
});
// --- reset on slice/image changes --- //
watch([currentSlice, currentImageID, widget], () => {
if (widget.value) {
widget.value.resetInteractions();
widget.value.setInteractionState(InteractionState.PlacingFirst);
}
});
// --- placed event --- //
onVTKEvent(widget, 'onPlacedEvent', () => {
const { firstPoint, secondPoint } = syncedState;
if (!firstPoint.origin || !secondPoint.origin)
throw new Error('Incomplete placing widget state');
const initState: RectangleInitState = {
firstPoint: firstPoint.origin,
secondPoint: secondPoint.origin,
};
emit('placed', initState);
widget.value?.resetState();
widget.value?.setInteractionState(InteractionState.PlacingFirst);
});
// --- manipulator --- //
const manipulator = vtkPlaneManipulator.newInstance();
onMounted(() => {
if (!widget.value) {
return;
}
widget.value.setManipulator(manipulator);
});
watchEffect(() => {
updatePlaneManipulatorFor2DView(
manipulator,
viewDirection.value,
currentSlice.value,
currentImageMetadata.value
);
});
// --- visibility --- //
onMounted(() => {
if (!widget.value) {
return;
}
// hide handle visibility, but not picking visibility
widget.value.setHandleVisibility(false);
widgetManager.value.renderWidgets();
});
return {
firstPoint: computed(() => {
return syncedState.firstPoint.visible
? syncedState.firstPoint.origin
: null;
}),
secondPoint: computed(() => {
return syncedState.secondPoint.visible
? syncedState.secondPoint.origin
: null;
}),
};
},
});
</script>

<template>
<SVG2DComponent
:view-id="viewId"
:point1="firstPoint"
:point2="secondPoint"
:color="color"
:fill-color="fillColor"
/>
</template>
5 changes: 3 additions & 2 deletions src/components/tools/rectangle/RectangleSVG2D.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
watch,
inject,
} from 'vue';
import { Maybe } from '@/src/types';
type SVGPoint = {
x: number;
Expand All @@ -57,8 +58,8 @@ type SVGPoint = {
export default defineComponent({
props: {
point1: Array as PropType<Array<number>>,
point2: Array as PropType<Array<number>>,
point1: Array as PropType<Maybe<Array<number>>>,
point2: Array as PropType<Maybe<Array<number>>>,
color: String,
fillColor: String,
viewId: {
Expand Down
144 changes: 39 additions & 105 deletions src/components/tools/rectangle/RectangleTool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
v-for="tool in tools"
:key="tool.id"
:tool-id="tool.id"
:is-placing="tool.id === placingToolID"
:current-slice="currentSlice"
:view-id="viewId"
:view-direction="viewDirection"
:widget-manager="widgetManager"
@contextmenu="openContextMenu(tool.id, $event)"
/>
<placing-rectangle-widget-2D
v-if="isToolActive"
:current-slice="currentSlice"
:color="activeLabelProps.color"
:fill-color="activeLabelProps.fillColor"
:view-id="viewId"
:view-direction="viewDirection"
:widget-manager="widgetManager"
@placed="onToolPlaced"
/>
</svg>
Expand All @@ -19,35 +27,25 @@
</template>

<script lang="ts">
import {
computed,
defineComponent,
onUnmounted,
PropType,
ref,
toRefs,
watch,
} from 'vue';
import { computed, defineComponent, PropType, toRefs } 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 {
useCurrentTools,
useContextMenu,
} from '@/src/composables/annotationTool';
import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue';
import PlacingRectangleWidget2D from '@/src/components/tools/rectangle/PlacingRectangleWidget2D.vue';
import { useCurrentFrameOfReference } from '@/src/composables/useCurrentFrameOfReference';
import { RectangleInitState } from '@/src/components/tools/rectangle/common';
import RectangleWidget2D from './RectangleWidget2D.vue';
type ToolID = RectangleID;
const useActiveToolStore = useRectangleStore;
const toolType = Tools.Rectangle;
Expand All @@ -73,6 +71,7 @@ export default defineComponent({
},
components: {
RectangleWidget2D,
PlacingRectangleWidget2D,
AnnotationContextMenu,
},
setup(props) {
Expand All @@ -81,114 +80,49 @@ export default defineComponent({
const activeToolStore = useActiveToolStore();
const { activeLabel } = storeToRefs(activeToolStore);
const { currentImageID, currentImageMetadata } = useCurrentImage();
const { currentImageID } = useCurrentImage();
const isToolActive = computed(() => toolStore.currentTool === toolType);
const viewAxis = computed(() => getLPSAxisFromDir(viewDirection.value));
const placingToolID = ref<ToolID | null>(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 }
);
watch(
[isToolActive, currentImageID] as const,
([active, imageID]) => {
if (placingToolID.value != null) {
activeToolStore.removeTool(placingToolID.value);
placingToolID.value = null;
}
if (active && imageID) {
placingToolID.value = activeToolStore.addTool({
imageID,
placing: true,
});
}
},
{ immediate: true }
const currentFrameOfReference = useCurrentFrameOfReference(
viewDirection,
currentSlice
);
watch(
[activeLabel, placingToolID],
([label, placingTool]) => {
if (placingTool != null) {
activeToolStore.updateTool(placingTool, {
label,
...(label && activeToolStore.labels[label]),
});
}
},
{ immediate: true }
);
onUnmounted(() => {
if (placingToolID.value != null) {
activeToolStore.removeTool(placingToolID.value);
placingToolID.value = null;
}
});
const onToolPlaced = () => {
if (currentImageID.value) {
placingToolID.value = activeToolStore.addTool({
imageID: currentImageID.value,
placing: true,
});
}
const onToolPlaced = (initState: RectangleInitState) => {
if (!currentImageID.value) return;
activeToolStore.addTool({
imageID: currentImageID.value,
frameOfReference: currentFrameOfReference.value,
slice: currentSlice.value,
label: activeLabel.value,
color: activeLabel.value
? activeToolStore.labels[activeLabel.value].color
: undefined,
firstPoint: initState.firstPoint,
secondPoint: initState.secondPoint,
});
};
// --- 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,
};
};
// 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: getCurrentFrameOfReference(),
slice,
});
},
{ immediate: true }
);
// --- right-click menu --- //
const { contextMenu, openContextMenu } = useContextMenu();
// --- //
const currentTools = useCurrentTools(activeToolStore, viewAxis);
const activeLabelProps = computed(() => {
return activeLabel.value ? activeToolStore.labels[activeLabel.value] : {};
});
return {
tools: currentTools,
placingToolID,
isToolActive,
onToolPlaced,
contextMenu,
openContextMenu,
activeToolStore,
activeLabelProps,
};
},
});
Expand Down
Loading

0 comments on commit ba3193b

Please sign in to comment.