Skip to content

Commit

Permalink
feat(polygon): separate out placing polygon
Browse files Browse the repository at this point in the history
  • Loading branch information
floryst committed Sep 13, 2023
1 parent c483e97 commit e3ab0aa
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 181 deletions.
152 changes: 152 additions & 0 deletions src/components/tools/polygon/PlacingPolygonWidget2D.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<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 vtkWidgetFactory, {
vtkPolygonViewWidget,
vtkPolygonWidgetState,
} from '@/src/vtk/PolygonWidget';
import createStandaloneState from '@/src/vtk/PolygonWidget/standaloneState';
import { useViewWidget } from '@/src/composables/useViewWidget';
import {
useSyncedPolygonState,
PolygonInitState,
} from '@/src/components/tools/polygon/common';
import SVG2DComponent from './PolygonSVG2D.vue';
export default defineComponent({
name: 'PlacingPolygonWidget2D',
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,
},
components: {
SVG2DComponent,
},
setup(props, { emit }) {
const { widgetManager, viewDirection, currentSlice } = toRefs(props);
const { currentImageID, currentImageMetadata } = useCurrentImage();
const widgetState = createStandaloneState() as vtkPolygonWidgetState;
const widgetFactory = vtkWidgetFactory.newInstance({
widgetState,
});
const syncedState = useSyncedPolygonState(widgetFactory);
const widget = useViewWidget<vtkPolygonViewWidget>(
widgetFactory,
widgetManager
);
onMounted(() => {
widgetState.setPlacing(true);
});
onUnmounted(() => {
widgetFactory.delete();
});
// --- reset on slice/image changes --- //
watch([currentSlice, currentImageID, widget], () => {
if (widget.value) {
widget.value.resetInteractions();
widget.value.getWidgetState().clearHandleList();
}
});
onVTKEvent(widget, 'onPlacedEvent', () => {
const initState: PolygonInitState = {
points: syncedState.points,
};
emit('placed', initState);
widget.value?.reset();
widgetState.setPlacing(true);
});
// --- 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();
});
// // when movePoint/mouse changes, get finishable manually as its not in store
// const finishable = ref(false);
// // const movePoint = computed(() => tool.value?.movePoint);
// watch([movePoint], () => {
// finishable.value = !!widget.value?.getWidgetState().getFinishable();
// });
return {
state: syncedState,
finishable: computed(() => syncedState.finishable),
};
},
});
</script>

<template>
<SVG2DComponent
:view-id="viewId"
:points="state.points"
:color="color"
:move-point="state.movePoint"
placing
:finishable="finishable"
/>
</template>
3 changes: 2 additions & 1 deletion src/components/tools/polygon/PolygonSVG2D.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
watch,
inject,
} from 'vue';
import { Maybe } from '@/src/types';
const POINT_RADIUS = 10;
const FINISHABLE_POINT_RADIUS = 16;
Expand All @@ -53,7 +54,7 @@ export default defineComponent({
required: true,
},
movePoint: {
type: Array as unknown as PropType<Vector3>,
type: Array as unknown as PropType<Maybe<Vector3>>,
},
placing: {
type: Boolean,
Expand Down
140 changes: 36 additions & 104 deletions src/components/tools/polygon/PolygonTool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
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-polygon-widget-2D
v-if="isToolActive"
:current-slice="currentSlice"
:color="activeLabelColor"
:view-id="viewId"
:view-direction="viewDirection"
:widget-manager="widgetManager"
@placed="onToolPlaced"
/>
</svg>
Expand All @@ -19,35 +26,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 { usePolygonStore } from '@/src/store/tools/polygons';
import { PolygonID } from '@/src/types/polygon';
import {
useContextMenu,
useCurrentTools,
} from '@/src/composables/annotationTool';
import { useCurrentFrameOfReference } from '@/src/composables/useCurrentFrameOfReference';
import AnnotationContextMenu from '@/src/components/tools/AnnotationContextMenu.vue';
import PlacingPolygonWidget2D from '@/src/components/tools/polygon/PlacingPolygonWidget2D.vue';
import { PolygonInitState } from '@/src/components/tools/polygon/common';
import PolygonWidget2D from './PolygonWidget2D.vue';
type ToolID = PolygonID;
const useActiveToolStore = usePolygonStore;
const toolType = Tools.Polygon;
Expand All @@ -73,6 +70,7 @@ export default defineComponent({
},
components: {
PolygonWidget2D,
PlacingPolygonWidget2D,
AnnotationContextMenu,
},
setup(props) {
Expand All @@ -81,109 +79,43 @@ 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 }
const currentFrameOfReference = useCurrentFrameOfReference(
viewDirection,
currentSlice
);
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 }
);
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: PolygonInitState) => {
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,
points: initState.points,
});
};
// --- 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,
};
};
// 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: getCurrentFrameOfReference(),
slice,
});
},
{ immediate: true }
);
// --- right-click menu --- //
const { contextMenu, openContextMenu } = useContextMenu();
const currentTools = useCurrentTools(activeToolStore, viewAxis);
return {
tools: currentTools,
placingToolID,
isToolActive,
activeLabelColor: computed(() => {
return (
activeLabel.value && activeToolStore.labels[activeLabel.value].color
);
}),
onToolPlaced,
contextMenu,
openContextMenu,
Expand Down
Loading

0 comments on commit e3ab0aa

Please sign in to comment.