Skip to content

Commit dc175f7

Browse files
committed
feat(ScalarProbe): move display to sidebar
1 parent 7f98bb9 commit dc175f7

File tree

4 files changed

+151
-85
lines changed

4 files changed

+151
-85
lines changed

src/components/ModulePanel.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
</v-window-item>
3636
</v-window>
3737
</div>
38+
<ProbeView />
3839
</div>
3940
</template>
4041

@@ -46,6 +47,7 @@ import DataBrowser from './DataBrowser.vue';
4647
import RenderingModule from './RenderingModule.vue';
4748
import AnnotationsModule from './AnnotationsModule.vue';
4849
import ServerModule from './ServerModule.vue';
50+
import ProbeView from './ProbeView.vue';
4951
import { useToolStore } from '../store/tools';
5052
import { Tools } from '../store/tools/types';
5153
@@ -88,6 +90,7 @@ const autoSwitchToAnnotationsTools = [
8890
8991
export default defineComponent({
9092
name: 'ModulePanel',
93+
components: { ProbeView },
9194
setup() {
9295
const selectedModuleIndex = ref(0);
9396

src/components/ProbeView.vue

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { useProbeStore } from '@/src/store/probe';
4+
import { storeToRefs } from 'pinia';
5+
6+
const probeStore = useProbeStore();
7+
const { probeData } = storeToRefs(probeStore);
8+
9+
const formattedProbeItems = computed(() => {
10+
if (!probeData.value) return [];
11+
const sampleItems = probeData.value.samples.map((sample) => ({
12+
label: sample.name,
13+
value: sample.displayValue.join(', '),
14+
}));
15+
16+
// Add additional item for Position
17+
const positionItem = {
18+
label: 'Position',
19+
value: probeData.value.pos.map(Math.round).join(', '),
20+
};
21+
22+
return [...sampleItems, positionItem];
23+
});
24+
</script>
25+
26+
<template>
27+
<v-card v-if="probeData" class="probe-value-display">
28+
<v-card-text>
29+
<div
30+
v-for="(item, index) in formattedProbeItems"
31+
:key="index"
32+
class="d-flex"
33+
>
34+
<span class="text-left text-truncate mr-2">
35+
{{ item.label }}
36+
</span>
37+
<span class="text-right ml-auto font-weight-bold">
38+
{{ item.value }}
39+
</span>
40+
</div>
41+
</v-card-text>
42+
</v-card>
43+
</template>
44+
45+
<style scoped>
46+
.probe-value-display {
47+
position: absolute;
48+
bottom: 0;
49+
right: 0;
50+
width: 100%;
51+
pointer-events: none;
52+
z-index: 1000;
53+
text-align: right;
54+
}
55+
</style>
Lines changed: 60 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { inject, ref, watch, computed, toRefs } from 'vue';
2+
import { inject, watch, computed, toRefs } from 'vue';
33
import { onVTKEvent } from '@/src/composables/onVTKEvent';
44
import { VtkViewContext } from '@/src/components/vtk/context';
55
import { useCurrentImage } from '@/src/composables/useCurrentImage';
@@ -8,6 +8,7 @@ import { useSliceRepresentation } from '@/src/core/vtk/useSliceRepresentation';
88
import { useImageStore } from '@/src/store/datasets-images';
99
import { useLayersStore } from '@/src/store/datasets-layers';
1010
import { useSegmentGroupStore } from '@/src/store/segmentGroups';
11+
import { useProbeStore } from '@/src/store/probe';
1112
1213
type SliceRepresentationType = ReturnType<typeof useSliceRepresentation>;
1314
@@ -18,7 +19,6 @@ const props = defineProps<{
1819
}>();
1920
2021
const { baseRep, layerReps, segmentGroupsReps } = toRefs(props);
21-
2222
const view = inject(VtkViewContext);
2323
if (!view) throw new Error('No VtkView');
2424
@@ -28,140 +28,115 @@ const {
2828
currentImageMetadata,
2929
currentLayers,
3030
} = useCurrentImage();
31-
3231
const imageStore = useImageStore();
3332
const layersStore = useLayersStore();
3433
const segmentGroupStore = useSegmentGroupStore();
35-
const sampleSet = computed(() => {
36-
const baseImage = currentImageData.value;
37-
if (!baseImage || !currentImageID.value) {
38-
return [];
39-
}
34+
const probeStore = useProbeStore();
4035
41-
const baseImageSlice = {
36+
// Helper functions to build a unified sample set
37+
const getBaseSlice = () => {
38+
if (!currentImageData.value || !currentImageID.value) return null;
39+
return {
4240
type: 'layer',
4341
id: currentImageID.value,
4442
name: currentImageMetadata.value.name,
4543
rep: baseRep.value,
46-
image: baseImage,
47-
} as const;
44+
image: currentImageData.value,
45+
};
46+
};
4847
49-
const layers = layerReps.value
50-
// filter out just deleted layers
51-
.filter((_, index) => currentLayers.value[index] !== undefined)
52-
.map((layerRep, index) => {
48+
const getLayers = () =>
49+
layerReps.value
50+
.map((rep, index) => {
5351
const layer = currentLayers.value[index];
52+
if (!layer) return null;
5453
return {
5554
type: 'layer',
5655
id: layer.id,
5756
name: imageStore.metadata[layer.selection].name,
58-
rep: layerRep,
57+
rep,
5958
image: layersStore.layerImages[layer.id],
60-
} as const;
61-
});
62-
63-
const segmentGroups = segmentGroupStore.orderByParent[currentImageID.value];
64-
const segments = segmentGroupsReps.value
65-
.map((group, index) => ({ group, index }))
66-
// filter out just deleted segment groups or on switching current image
67-
.filter(({ index }) => segmentGroups && segmentGroups[index])
68-
.map(({ group, index }) => {
69-
const id = segmentGroups![index];
70-
const meta = segmentGroupStore.metadataByID[id];
59+
};
60+
})
61+
.filter(Boolean);
62+
63+
const getSegments = () => {
64+
if (!currentImageID.value) return [];
65+
const parentGroups = segmentGroupStore.orderByParent[currentImageID.value];
66+
if (!parentGroups) return [];
67+
return segmentGroupsReps.value
68+
.map((rep, index) => {
69+
const groupId = parentGroups[index];
70+
if (!groupId) return null;
71+
const meta = segmentGroupStore.metadataByID[groupId];
7172
return {
7273
type: 'segmentGroup',
73-
id,
74+
id: groupId,
7475
name: meta.name,
75-
rep: group,
76+
rep,
7677
segments: meta.segments,
77-
image: segmentGroupStore.dataIndex[id],
78-
} as const;
79-
});
78+
image: segmentGroupStore.dataIndex[groupId],
79+
};
80+
})
81+
.filter(Boolean);
82+
};
8083
81-
return [...segments, ...layers, baseImageSlice];
84+
const sampleSet = computed(() => {
85+
const base = getBaseSlice();
86+
if (!base) return [];
87+
return [...getSegments(), ...getLayers(), base];
8288
});
8389
8490
const pointPicker = vtkPointPicker.newInstance();
8591
pointPicker.setPickFromList(true);
8692
8793
watch(
8894
sampleSet,
89-
(toPick) => {
90-
pointPicker.setPickList(toPick.map((item) => item.rep.actor));
95+
(samples) => {
96+
pointPicker.setPickList(samples.map((item: any) => item.rep.actor));
9197
},
9298
{ immediate: true }
9399
);
94100
95101
const getImageSamples = (x: number, y: number) => {
96102
pointPicker.pick([x, y, 1.0], view.renderer);
97-
if (pointPicker.getActors().length === 0) {
98-
return undefined;
99-
}
103+
if (pointPicker.getActors().length === 0) return undefined;
104+
100105
const ijk = pointPicker.getPointIJK();
101-
const samples = sampleSet.value.map((toSample) => {
102-
const size = toSample.image.getDimensions();
103-
const scalarData = toSample.image.getPointData().getScalars();
104-
const scalars = scalarData.getTuple(
105-
size[0] * size[1] * ijk[2] + size[0] * ijk[1] + ijk[0]
106-
) as number[];
107-
const idName = {
108-
id: toSample.id,
109-
name: toSample.name,
110-
};
111-
if (toSample.type === 'segmentGroup') {
106+
const samples = sampleSet.value.map((item: any) => {
107+
const dims = item.image.getDimensions();
108+
const scalarData = item.image.getPointData().getScalars();
109+
const index = dims[0] * dims[1] * ijk[2] + dims[0] * ijk[1] + ijk[0];
110+
const scalars = scalarData.getTuple(index) as number[];
111+
const baseInfo = { id: item.id, name: item.name };
112+
113+
if (item.type === 'segmentGroup') {
112114
return {
113-
...idName,
115+
...baseInfo,
114116
displayValue: scalars.map(
115-
(v) => toSample.segments.byValue[v]?.name || 'Background'
117+
(v) => item.segments.byValue[v]?.name || 'Background'
116118
),
117119
};
118120
}
119-
return {
120-
...idName,
121-
displayValue: scalars,
122-
};
121+
return { ...baseInfo, displayValue: scalars };
123122
});
124-
const pos = pointPicker.getPickPosition();
123+
125124
return {
126-
pos,
125+
pos: pointPicker.getPickPosition(),
127126
samples,
128127
};
129128
};
130129
131-
const samples = ref<ReturnType<typeof getImageSamples> | undefined>(undefined);
132-
133130
onVTKEvent(view.interactor, 'onMouseMove', (event: any) => {
134-
samples.value = getImageSamples(event.position.x, event.position.y);
131+
const samples = getImageSamples(event.position.x, event.position.y);
132+
probeStore.updateProbeData(samples);
135133
});
136134
137135
onVTKEvent(view.interactor, 'onPointerLeave', () => {
138-
samples.value = undefined;
136+
probeStore.clearProbeData();
139137
});
140138
141139
watch([currentImageID, sampleSet], () => {
142-
samples.value = undefined;
140+
probeStore.clearProbeData();
143141
});
144142
</script>
145-
146-
<template>
147-
<div v-if="samples !== undefined" class="probe-value-display">
148-
<div v-for="sample in samples.samples" :key="sample.id">
149-
<div>{{ sample.name }}: {{ sample.displayValue.join(', ') }}</div>
150-
</div>
151-
<div>Position: {{ `${samples.pos.map(Math.round).join(', ')}` }}</div>
152-
</div>
153-
</template>
154-
155-
<style scoped>
156-
.probe-value-display {
157-
position: absolute;
158-
bottom: 10px;
159-
right: 10px;
160-
background-color: rgba(0, 0, 0, 0.7);
161-
color: white;
162-
padding: 5px 10px;
163-
border-radius: 4px;
164-
font-size: 12px;
165-
pointer-events: none;
166-
}
167-
</style>

src/store/probe.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ref } from 'vue';
2+
import { defineStore } from 'pinia';
3+
4+
export type ProbeSample = {
5+
id: string;
6+
name: string;
7+
displayValue: (string | number)[];
8+
};
9+
10+
export type ProbeData =
11+
| {
12+
pos: number[];
13+
samples: ProbeSample[];
14+
}
15+
| undefined;
16+
17+
export const useProbeStore = defineStore('probe', () => {
18+
const probeData = ref<ProbeData>(undefined);
19+
20+
const updateProbeData = (data: ProbeData) => {
21+
probeData.value = data;
22+
};
23+
24+
const clearProbeData = () => {
25+
probeData.value = undefined;
26+
};
27+
28+
return {
29+
probeData,
30+
updateProbeData,
31+
clearProbeData,
32+
};
33+
});

0 commit comments

Comments
 (0)