Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify placing tools #414

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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