Skip to content

Commit 56673f2

Browse files
authored
Merge pull request Kitware#670 from PaulHax/seg-outline
Outline segment group rendering
2 parents dadc30c + 2c185ba commit 56673f2

14 files changed

+322
-73
lines changed

package-lock.json

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@aws-sdk/client-s3": "^3.435.0",
2929
"@itk-wasm/dicom": "7.2.2",
3030
"@itk-wasm/image-io": "^1.3.0",
31-
"@kitware/vtk.js": "^29.0.0",
31+
"@kitware/vtk.js": "^32.6.2",
3232
"@netlify/edge-functions": "^2.0.0",
3333
"@sentry/vue": "^7.54.0",
3434
"@velipso/polybool": "^2.0.11",

src/components/SegmentEditor.vue

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22
import LabelEditor from '@/src/components/LabelEditor.vue';
33
import { computed } from 'vue';
44
5-
defineEmits(['done', 'cancel', 'delete', 'update:name', 'update:color']);
5+
defineEmits([
6+
'done',
7+
'cancel',
8+
'delete',
9+
'update:name',
10+
'update:color',
11+
'update:opacity',
12+
]);
613
714
const props = defineProps<{
815
name: string;
916
color: string;
1017
invalidNames: Set<string>;
18+
opacity: number;
1119
}>();
1220
1321
function isUniqueEditingName(name: string) {
@@ -41,6 +49,18 @@ const valid = computed(() => {
4149
@keydown.stop.enter="done"
4250
:rules="[uniqueNameRule]"
4351
/>
52+
<v-slider
53+
class="mx-4 my-1"
54+
label="Segment Fill Opacity"
55+
min="0"
56+
max="1"
57+
step="0.01"
58+
density="compact"
59+
hide-details
60+
thumb-label
61+
:model-value="opacity"
62+
@update:model-value="$emit('update:opacity', $event)"
63+
/>
4464
</template>
4565
</label-editor>
4666
</template>

src/components/SegmentGroupControls.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,6 @@ function openSaveDialog(id: string) {
208208
<segment-group-opacity
209209
v-if="currentSegmentGroupID"
210210
:group-id="currentSegmentGroupID"
211-
class="my-1"
212211
/>
213212
<v-radio-group
214213
v-model="currentSegmentGroupID"

src/components/SegmentGroupOpacity.vue

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { computed, toRefs } from 'vue';
33
import { useGlobalLayerColorConfig } from '@/src/composables/useGlobalLayerColorConfig';
4+
import { useGlobalSegmentGroupConfig } from '@/src/store/view-configs/segmentGroups';
45
56
const props = defineProps<{
67
groupId: string;
@@ -21,12 +22,33 @@ const setOpacity = (opacity: number) => {
2122
},
2223
});
2324
};
25+
26+
const { config, updateConfig: updateSegmentGroupConfig } =
27+
useGlobalSegmentGroupConfig(groupId);
28+
29+
const outlineOpacity = computed({
30+
get: () => config.value!.config!.outlineOpacity,
31+
set: (opacity: number) => {
32+
updateSegmentGroupConfig({
33+
outlineOpacity: opacity,
34+
});
35+
},
36+
});
37+
38+
const outlineThickness = computed({
39+
get: () => config.value!.config!.outlineThickness,
40+
set: (thickness: number) => {
41+
updateSegmentGroupConfig({
42+
outlineThickness: thickness,
43+
});
44+
},
45+
});
2446
</script>
2547

2648
<template>
2749
<v-slider
2850
class="mx-4"
29-
label="Segment Group Opacity"
51+
label="Segment Group Fill Opacity"
3052
min="0"
3153
max="1"
3254
step="0.01"
@@ -36,4 +58,26 @@ const setOpacity = (opacity: number) => {
3658
:model-value="blendConfig.opacity"
3759
@update:model-value="setOpacity($event)"
3860
/>
61+
<v-slider
62+
class="mx-4"
63+
label="Segment Group Outline Opacity"
64+
min="0"
65+
max="1"
66+
step="0.01"
67+
density="compact"
68+
hide-details
69+
thumb-label
70+
v-model="outlineOpacity"
71+
/>
72+
<v-slider
73+
class="mx-4"
74+
label="Segment Group Outline Thickness"
75+
min="0"
76+
max="10"
77+
step="1"
78+
density="compact"
79+
hide-details
80+
thumb-label
81+
v-model="outlineThickness"
82+
/>
3983
</template>

src/components/SegmentList.vue

Lines changed: 11 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { hexaToRGBA, rgbaToHexa } from '@/src/utils/color';
1111
import { reactive, ref, toRefs, computed, watch } from 'vue';
1212
import { SegmentMask } from '@/src/types/segment';
1313
import { usePaintToolStore } from '@/src/store/tools/paint';
14-
import { RGBAColor } from '@kitware/vtk.js/types';
14+
import type { RGBAColor } from '@kitware/vtk.js/types';
1515
1616
const props = defineProps({
1717
groupId: {
@@ -58,36 +58,6 @@ watch(
5858
{ immediate: true }
5959
);
6060
61-
// --- segment opacity --- //
62-
63-
const selectedSegmentMask = computed(() => {
64-
if (!selectedSegment.value) return null;
65-
return segmentGroupStore.getSegment(groupId.value, selectedSegment.value);
66-
});
67-
68-
const segmentOpacity = computed(() => {
69-
if (!selectedSegmentMask.value) return 1;
70-
return selectedSegmentMask.value.color[3] / 255;
71-
});
72-
73-
const setSegmentOpacity = (opacity: number) => {
74-
if (!selectedSegmentMask.value) {
75-
return;
76-
}
77-
78-
const color = selectedSegmentMask.value.color;
79-
segmentGroupStore.updateSegment(
80-
groupId.value,
81-
selectedSegmentMask.value.value,
82-
{
83-
color: [
84-
...(color.slice(0, 3) as [number, number, number]),
85-
Math.round(opacity * 255),
86-
],
87-
}
88-
);
89-
};
90-
9161
const toggleVisible = (value: number) => {
9262
const segment = segmentGroupStore.getSegment(groupId.value, value);
9363
if (!segment) return;
@@ -116,6 +86,7 @@ const editingSegmentValue = ref<Maybe<number>>(null);
11686
const editState = reactive({
11787
name: '',
11888
color: '',
89+
opacity: 1,
11990
});
12091
const editDialog = ref(false);
12192
@@ -136,14 +107,20 @@ function startEditing(value: number) {
136107
if (!editingSegment.value) return;
137108
editState.name = editingSegment.value.name;
138109
editState.color = rgbaToHexa(editingSegment.value.color);
110+
editState.opacity = editingSegment.value.color[3] / 255;
139111
}
140112
141113
function stopEditing(commit: boolean) {
142-
if (editingSegmentValue.value && commit)
114+
if (editingSegmentValue.value && commit) {
115+
const color = [
116+
...(hexaToRGBA(editState.color).slice(0, 3) as [number, number, number]),
117+
Math.round(editState.opacity * 255),
118+
] as RGBAColor;
143119
segmentGroupStore.updateSegment(groupId.value, editingSegmentValue.value, {
144120
name: editState.name ?? makeDefaultSegmentName(editingSegmentValue.value),
145-
color: hexaToRGBA(editState.color),
121+
color,
146122
});
123+
}
147124
editingSegmentValue.value = null;
148125
editDialog.value = false;
149126
}
@@ -170,19 +147,6 @@ function deleteEditingSegment() {
170147
</slot>
171148
</v-btn>
172149

173-
<v-slider
174-
class="mx-4 my-1"
175-
label="Segment Opacity"
176-
min="0"
177-
max="1"
178-
step="0.01"
179-
density="compact"
180-
hide-details
181-
thumb-label
182-
:model-value="segmentOpacity"
183-
@update:model-value="setSegmentOpacity($event)"
184-
/>
185-
186150
<editable-chip-list
187151
v-model="selectedSegment"
188152
:items="segments"
@@ -242,6 +206,7 @@ function deleteEditingSegment() {
242206
v-if="!!editingSegment"
243207
v-model:name="editState.name"
244208
v-model:color="editState.color"
209+
v-model:opacity="editState.opacity"
245210
@delete="deleteEditingSegment"
246211
@cancel="stopEditing(false)"
247212
@done="stopEditing(true)"

src/components/vtk/VtkLayerSliceRepresentation.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ const applyLayerColoring = () => {
7373
if (!config) return;
7474
7575
const cfun = sliceRep.property.getRGBTransferFunction(0);
76-
const ofun = sliceRep.property.getScalarOpacity(0);
76+
const ofun = sliceRep.property.getPiecewiseFunction(0);
77+
78+
if (!cfun || !ofun) throw new Error('Missing transfer functions');
7779
7880
applyColoring({
7981
props: {

src/components/vtk/VtkSegmentationSliceRepresentation.vue

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { toRefs, watchEffect, inject, computed } from 'vue';
2+
import { toRefs, watchEffect, inject, computed, unref } from 'vue';
33
import { useImage } from '@/src/composables/useCurrentImage';
44
import { useSliceRepresentation } from '@/src/core/vtk/useSliceRepresentation';
55
import { LPSAxis } from '@/src/types/lps';
@@ -17,6 +17,7 @@ import { vtkFieldRef } from '@/src/core/vtk/vtkFieldRef';
1717
import { syncRef } from '@vueuse/core';
1818
import { useSliceConfig } from '@/src/composables/useSliceConfig';
1919
import useLayerColoringStore from '@/src/store/view-configs/layers';
20+
import { useSegmentGroupConfigStore } from '@/src/store/view-configs/segmentGroups';
2021
import { useSegmentGroupConfigInitializer } from '@/src/composables/useSegmentGroupConfigInitializer';
2122
2223
interface Props {
@@ -104,6 +105,8 @@ const applySegmentColoring = () => {
104105
const cfun = sliceRep.property.getRGBTransferFunction(0);
105106
const ofun = sliceRep.property.getPiecewiseFunction(0);
106107
108+
if (!cfun || !ofun) throw new Error('Missing transfer functions');
109+
107110
cfun.removeAllPoints();
108111
ofun.removeAllPoints();
109112
@@ -135,6 +138,33 @@ const applySegmentColoring = () => {
135138
136139
watchEffect(applySegmentColoring);
137140
141+
const configStore = useSegmentGroupConfigStore();
142+
const config = computed(() =>
143+
configStore.getConfig(unref(viewId), unref(segmentationId))
144+
);
145+
146+
const outlineThickness = computed(() => config.value?.outlineThickness ?? 2);
147+
sliceRep.property.setUseLabelOutline(true);
148+
sliceRep.property.setUseLookupTableScalarRange(true);
149+
150+
watchEffect(() => {
151+
sliceRep.property.setLabelOutlineOpacity(config.value?.outlineOpacity ?? 1);
152+
});
153+
154+
watchEffect(() => {
155+
if (!metadata.value) return; // segment group just deleted
156+
157+
const thickness = outlineThickness.value;
158+
const { segments } = metadata.value;
159+
const largestValue = Math.max(...segments.order);
160+
161+
const segThicknesses = Array.from({ length: largestValue }, (_, value) => {
162+
const segment = segments.byValue[value + 1];
163+
return ((!segment || segment.visible) && thickness) || 0;
164+
});
165+
sliceRep.property.setLabelOutlineThickness(segThicknesses);
166+
});
167+
138168
defineExpose(sliceRep);
139169
</script>
140170

src/composables/useSegmentGroupConfigInitializer.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import useLayerColoringStore from '@/src/store/view-configs/layers';
21
import { watchImmediate } from '@vueuse/core';
32
import { MaybeRef, computed, unref } from 'vue';
3+
import useLayerColoringStore from '@/src/store/view-configs/layers';
4+
import { useSegmentGroupConfigStore } from '@/src/store/view-configs/segmentGroups';
45

5-
export function useSegmentGroupConfigInitializer(
6+
function useLayerConfigInitializerForSegmentGroups(
67
viewId: MaybeRef<string>,
78
layerId: MaybeRef<string>
89
) {
@@ -16,6 +17,28 @@ export function useSegmentGroupConfigInitializer(
1617

1718
const viewIdVal = unref(viewId);
1819
const layerIdVal = unref(layerId);
19-
coloringStore.initConfig(viewIdVal, layerIdVal);
20+
coloringStore.initConfig(viewIdVal, layerIdVal); // initConfig instead of resetColorPreset for layers
21+
coloringStore.updateBlendConfig(viewIdVal, layerIdVal, {
22+
opacity: 0.3,
23+
});
24+
});
25+
}
26+
27+
export function useSegmentGroupConfigInitializer(
28+
viewId: MaybeRef<string>,
29+
segmentGroupId: MaybeRef<string>
30+
) {
31+
useLayerConfigInitializerForSegmentGroups(viewId, segmentGroupId);
32+
33+
const configStore = useSegmentGroupConfigStore();
34+
const config = computed(() =>
35+
configStore.getConfig(unref(viewId), unref(segmentGroupId))
36+
);
37+
38+
watchImmediate(config, (config_) => {
39+
if (config_) return;
40+
const viewIdVal = unref(viewId);
41+
const layerIdVal = unref(segmentGroupId);
42+
configStore.initConfig(viewIdVal, layerIdVal);
2043
});
2144
}

0 commit comments

Comments
 (0)