Skip to content

Commit 126597b

Browse files
committed
feat: add scalar probe tool
1 parent 1f3102a commit 126597b

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

src/components/SliceViewer.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
:image-id="currentImageID"
8181
></slice-viewer-overlay>
8282
<vtk-base-slice-representation
83+
ref="baseSliceRep"
8384
:view-id="id"
8485
:image-id="currentImageID"
8586
:axis="viewAxis"
@@ -131,6 +132,7 @@
131132
<svg class="overlay-no-events">
132133
<bounding-rectangle :points="selectionPoints" />
133134
</svg>
135+
<scalar-probe :slice-representation="baseSliceRep" />
134136
<slot></slot>
135137
</vtk-slice-view>
136138
</div>
@@ -168,6 +170,7 @@ import PolygonTool from '@/src/components/tools/polygon/PolygonTool.vue';
168170
import RulerTool from '@/src/components/tools/ruler/RulerTool.vue';
169171
import RectangleTool from '@/src/components/tools/rectangle/RectangleTool.vue';
170172
import SelectTool from '@/src/components/tools/SelectTool.vue';
173+
import ScalarProbe from '@/src/components/tools/ScalarProbe.vue';
171174
import BoundingRectangle from '@/src/components/tools/BoundingRectangle.vue';
172175
import SliceSlider from '@/src/components/SliceSlider.vue';
173176
import SliceViewerOverlay from '@/src/components/SliceViewerOverlay.vue';
@@ -256,6 +259,8 @@ const selectionPoints = computed(() => {
256259
)
257260
.flatMap(({ store, tool }) => store.getPoints(tool.id));
258261
});
262+
263+
const baseSliceRep = ref();
259264
</script>
260265

261266
<style scoped src="@/src/components/styles/vtk-view.css"></style>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<script setup lang="ts">
2+
import { inject, ref, watch, computed, toRefs } from 'vue';
3+
import type { Vector3 } from '@kitware/vtk.js/types';
4+
import { onVTKEvent } from '@/src/composables/onVTKEvent';
5+
import { WIDGET_PRIORITY } from '@kitware/vtk.js/Widgets/Core/AbstractWidget/Constants';
6+
import { VtkViewContext } from '@/src/components/vtk/context';
7+
import { useCurrentImage } from '@/src/composables/useCurrentImage';
8+
import vtkPointPicker from '@kitware/vtk.js/Rendering/Core/PointPicker';
9+
import { useSliceRepresentation } from '@/src/core/vtk/useSliceRepresentation';
10+
11+
type SliceRepresentationType = ReturnType<typeof useSliceRepresentation>;
12+
13+
const props = defineProps<{
14+
sliceRepresentation: SliceRepresentationType;
15+
}>();
16+
17+
const { sliceRepresentation } = toRefs(props);
18+
19+
const view = inject(VtkViewContext);
20+
if (!view) throw new Error('No VtkView');
21+
22+
const { currentImageID, currentImageData, currentImageMetadata } =
23+
useCurrentImage();
24+
25+
const sampleSet = computed(() => {
26+
const image = currentImageData.value;
27+
if (!image || !currentImageID.value) {
28+
return [];
29+
}
30+
return [
31+
{
32+
id: currentImageID.value,
33+
name: currentImageMetadata.value.name,
34+
rep: sliceRepresentation.value,
35+
image,
36+
},
37+
];
38+
});
39+
40+
// Setup point picker
41+
const pointPicker = vtkPointPicker.newInstance();
42+
pointPicker.setPickFromList(1); // don't pick from polygon actors
43+
44+
watch(
45+
sampleSet,
46+
(toPick) => {
47+
pointPicker.initializePickList();
48+
49+
toPick.forEach((item) => {
50+
pointPicker.addPickList(item.rep.actor);
51+
});
52+
},
53+
{
54+
immediate: true,
55+
}
56+
);
57+
58+
const getImageSamples = (x: number, y: number) => {
59+
pointPicker.pick([x, y, 1.0], view.renderer);
60+
61+
if (pointPicker.getActors().length === 0) {
62+
return null;
63+
}
64+
65+
const pos = pointPicker.getPickPosition();
66+
const ijk = pointPicker.getPointIJK();
67+
68+
// Map over all samples to get values at the same position
69+
const samples = sampleSet.value.map((item) => {
70+
const image = item.image;
71+
const size = image.getDimensions();
72+
const scalarData = image.getPointData().getScalars();
73+
const scalars = scalarData.getTuple(
74+
size[0] * size[1] * ijk[2] + size[0] * ijk[1] + ijk[0]
75+
) as number[];
76+
77+
return {
78+
id: item.id,
79+
name: item.name,
80+
scalars,
81+
pos,
82+
};
83+
});
84+
85+
return samples;
86+
};
87+
88+
// Update the sample ref type
89+
type SampleItem = {
90+
id: string;
91+
name: string;
92+
scalars: number[];
93+
pos: Vector3;
94+
};
95+
const sample = ref<SampleItem[] | null>(null);
96+
97+
onVTKEvent(
98+
view.interactor,
99+
'onMouseMove',
100+
(event: any) => {
101+
const position = event.position;
102+
103+
const value = getImageSamples(position.x, position.y);
104+
sample.value = value;
105+
},
106+
{
107+
// Ensure handler is called before widgets
108+
priority: WIDGET_PRIORITY + 2,
109+
}
110+
);
111+
</script>
112+
113+
<template>
114+
<div v-if="sample !== null" class="probe-value-display">
115+
<div v-for="item in sample" :key="item.id">
116+
<div>{{ item.name }}: {{ item.scalars.join(', ') }}</div>
117+
<div>
118+
Position:
119+
{{ item ? `(${item.pos.map(Math.round).join(', ')})` : '' }}
120+
</div>
121+
</div>
122+
</div>
123+
</template>
124+
125+
<style scoped>
126+
.probe-value-display {
127+
position: absolute;
128+
bottom: 10px;
129+
right: 10px;
130+
background-color: rgba(0, 0, 0, 0.7);
131+
color: white;
132+
padding: 5px 10px;
133+
border-radius: 4px;
134+
font-size: 12px;
135+
pointer-events: none;
136+
}
137+
</style>

0 commit comments

Comments
 (0)