Skip to content

Commit

Permalink
Merge pull request #670 from PaulHax/seg-outline
Browse files Browse the repository at this point in the history
Outline segment group rendering
  • Loading branch information
PaulHax authored Nov 13, 2024
2 parents 49edb19 + c54adc3 commit 0021740
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 73 deletions.
17 changes: 9 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@aws-sdk/client-s3": "^3.435.0",
"@itk-wasm/dicom": "7.2.2",
"@itk-wasm/image-io": "^1.3.0",
"@kitware/vtk.js": "^29.0.0",
"@kitware/vtk.js": "^32.6.2",
"@netlify/edge-functions": "^2.0.0",
"@sentry/vue": "^7.54.0",
"@velipso/polybool": "^2.0.11",
Expand Down
22 changes: 21 additions & 1 deletion src/components/SegmentEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@
import LabelEditor from '@/src/components/LabelEditor.vue';
import { computed } from 'vue';
defineEmits(['done', 'cancel', 'delete', 'update:name', 'update:color']);
defineEmits([
'done',
'cancel',
'delete',
'update:name',
'update:color',
'update:opacity',
]);
const props = defineProps<{
name: string;
color: string;
invalidNames: Set<string>;
opacity: number;
}>();
function isUniqueEditingName(name: string) {
Expand Down Expand Up @@ -41,6 +49,18 @@ const valid = computed(() => {
@keydown.stop.enter="done"
:rules="[uniqueNameRule]"
/>
<v-slider
class="mx-4 my-1"
label="Segment Fill Opacity"
min="0"
max="1"
step="0.01"
density="compact"
hide-details
thumb-label
:model-value="opacity"
@update:model-value="$emit('update:opacity', $event)"
/>
</template>
</label-editor>
</template>
1 change: 0 additions & 1 deletion src/components/SegmentGroupControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ function openSaveDialog(id: string) {
<segment-group-opacity
v-if="currentSegmentGroupID"
:group-id="currentSegmentGroupID"
class="my-1"
/>
<v-radio-group
v-model="currentSegmentGroupID"
Expand Down
46 changes: 45 additions & 1 deletion src/components/SegmentGroupOpacity.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, toRefs } from 'vue';
import { useGlobalLayerColorConfig } from '@/src/composables/useGlobalLayerColorConfig';
import { useGlobalSegmentGroupConfig } from '@/src/store/view-configs/segmentGroups';
const props = defineProps<{
groupId: string;
Expand All @@ -21,12 +22,33 @@ const setOpacity = (opacity: number) => {
},
});
};
const { config, updateConfig: updateSegmentGroupConfig } =
useGlobalSegmentGroupConfig(groupId);
const outlineOpacity = computed({
get: () => config.value!.config!.outlineOpacity,
set: (opacity: number) => {
updateSegmentGroupConfig({
outlineOpacity: opacity,
});
},
});
const outlineThickness = computed({
get: () => config.value!.config!.outlineThickness,
set: (thickness: number) => {
updateSegmentGroupConfig({
outlineThickness: thickness,
});
},
});
</script>

<template>
<v-slider
class="mx-4"
label="Segment Group Opacity"
label="Segment Group Fill Opacity"
min="0"
max="1"
step="0.01"
Expand All @@ -36,4 +58,26 @@ const setOpacity = (opacity: number) => {
:model-value="blendConfig.opacity"
@update:model-value="setOpacity($event)"
/>
<v-slider
class="mx-4"
label="Segment Group Outline Opacity"
min="0"
max="1"
step="0.01"
density="compact"
hide-details
thumb-label
v-model="outlineOpacity"
/>
<v-slider
class="mx-4"
label="Segment Group Outline Thickness"
min="0"
max="10"
step="1"
density="compact"
hide-details
thumb-label
v-model="outlineThickness"
/>
</template>
57 changes: 11 additions & 46 deletions src/components/SegmentList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { hexaToRGBA, rgbaToHexa } from '@/src/utils/color';
import { reactive, ref, toRefs, computed, watch } from 'vue';
import { SegmentMask } from '@/src/types/segment';
import { usePaintToolStore } from '@/src/store/tools/paint';
import { RGBAColor } from '@kitware/vtk.js/types';
import type { RGBAColor } from '@kitware/vtk.js/types';
const props = defineProps({
groupId: {
Expand Down Expand Up @@ -58,36 +58,6 @@ watch(
{ immediate: true }
);
// --- segment opacity --- //
const selectedSegmentMask = computed(() => {
if (!selectedSegment.value) return null;
return segmentGroupStore.getSegment(groupId.value, selectedSegment.value);
});
const segmentOpacity = computed(() => {
if (!selectedSegmentMask.value) return 1;
return selectedSegmentMask.value.color[3] / 255;
});
const setSegmentOpacity = (opacity: number) => {
if (!selectedSegmentMask.value) {
return;
}
const color = selectedSegmentMask.value.color;
segmentGroupStore.updateSegment(
groupId.value,
selectedSegmentMask.value.value,
{
color: [
...(color.slice(0, 3) as [number, number, number]),
Math.round(opacity * 255),
],
}
);
};
const toggleVisible = (value: number) => {
const segment = segmentGroupStore.getSegment(groupId.value, value);
if (!segment) return;
Expand Down Expand Up @@ -116,6 +86,7 @@ const editingSegmentValue = ref<Maybe<number>>(null);
const editState = reactive({
name: '',
color: '',
opacity: 1,
});
const editDialog = ref(false);
Expand All @@ -136,14 +107,20 @@ function startEditing(value: number) {
if (!editingSegment.value) return;
editState.name = editingSegment.value.name;
editState.color = rgbaToHexa(editingSegment.value.color);
editState.opacity = editingSegment.value.color[3] / 255;
}
function stopEditing(commit: boolean) {
if (editingSegmentValue.value && commit)
if (editingSegmentValue.value && commit) {
const color = [
...(hexaToRGBA(editState.color).slice(0, 3) as [number, number, number]),
Math.round(editState.opacity * 255),
] as RGBAColor;
segmentGroupStore.updateSegment(groupId.value, editingSegmentValue.value, {
name: editState.name ?? makeDefaultSegmentName(editingSegmentValue.value),
color: hexaToRGBA(editState.color),
color,
});
}
editingSegmentValue.value = null;
editDialog.value = false;
}
Expand All @@ -170,19 +147,6 @@ function deleteEditingSegment() {
</slot>
</v-btn>

<v-slider
class="mx-4 my-1"
label="Segment Opacity"
min="0"
max="1"
step="0.01"
density="compact"
hide-details
thumb-label
:model-value="segmentOpacity"
@update:model-value="setSegmentOpacity($event)"
/>

<editable-chip-list
v-model="selectedSegment"
:items="segments"
Expand Down Expand Up @@ -242,6 +206,7 @@ function deleteEditingSegment() {
v-if="!!editingSegment"
v-model:name="editState.name"
v-model:color="editState.color"
v-model:opacity="editState.opacity"
@delete="deleteEditingSegment"
@cancel="stopEditing(false)"
@done="stopEditing(true)"
Expand Down
4 changes: 3 additions & 1 deletion src/components/vtk/VtkLayerSliceRepresentation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ const applyLayerColoring = () => {
if (!config) return;
const cfun = sliceRep.property.getRGBTransferFunction(0);
const ofun = sliceRep.property.getScalarOpacity(0);
const ofun = sliceRep.property.getPiecewiseFunction(0);
if (!cfun || !ofun) throw new Error('Missing transfer functions');
applyColoring({
props: {
Expand Down
32 changes: 31 additions & 1 deletion src/components/vtk/VtkSegmentationSliceRepresentation.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { toRefs, watchEffect, inject, computed } from 'vue';
import { toRefs, watchEffect, inject, computed, unref } from 'vue';
import { useImage } from '@/src/composables/useCurrentImage';
import { useSliceRepresentation } from '@/src/core/vtk/useSliceRepresentation';
import { LPSAxis } from '@/src/types/lps';
Expand All @@ -17,6 +17,7 @@ import { vtkFieldRef } from '@/src/core/vtk/vtkFieldRef';
import { syncRef } from '@vueuse/core';
import { useSliceConfig } from '@/src/composables/useSliceConfig';
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { useSegmentGroupConfigStore } from '@/src/store/view-configs/segmentGroups';
import { useSegmentGroupConfigInitializer } from '@/src/composables/useSegmentGroupConfigInitializer';
interface Props {
Expand Down Expand Up @@ -104,6 +105,8 @@ const applySegmentColoring = () => {
const cfun = sliceRep.property.getRGBTransferFunction(0);
const ofun = sliceRep.property.getPiecewiseFunction(0);
if (!cfun || !ofun) throw new Error('Missing transfer functions');
cfun.removeAllPoints();
ofun.removeAllPoints();
Expand Down Expand Up @@ -135,6 +138,33 @@ const applySegmentColoring = () => {
watchEffect(applySegmentColoring);
const configStore = useSegmentGroupConfigStore();
const config = computed(() =>
configStore.getConfig(unref(viewId), unref(segmentationId))
);
const outlineThickness = computed(() => config.value?.outlineThickness ?? 2);
sliceRep.property.setUseLabelOutline(true);
sliceRep.property.setUseLookupTableScalarRange(true);
watchEffect(() => {
sliceRep.property.setLabelOutlineOpacity(config.value?.outlineOpacity ?? 1);
});
watchEffect(() => {
if (!metadata.value) return; // segment group just deleted
const thickness = outlineThickness.value;
const { segments } = metadata.value;
const largestValue = Math.max(...segments.order);
const segThicknesses = Array.from({ length: largestValue }, (_, value) => {
const segment = segments.byValue[value + 1];
return ((!segment || segment.visible) && thickness) || 0;
});
sliceRep.property.setLabelOutlineThickness(segThicknesses);
});
defineExpose(sliceRep);
</script>

Expand Down
29 changes: 26 additions & 3 deletions src/composables/useSegmentGroupConfigInitializer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { watchImmediate } from '@vueuse/core';
import { MaybeRef, computed, unref } from 'vue';
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { useSegmentGroupConfigStore } from '@/src/store/view-configs/segmentGroups';

export function useSegmentGroupConfigInitializer(
function useLayerConfigInitializerForSegmentGroups(
viewId: MaybeRef<string>,
layerId: MaybeRef<string>
) {
Expand All @@ -16,6 +17,28 @@ export function useSegmentGroupConfigInitializer(

const viewIdVal = unref(viewId);
const layerIdVal = unref(layerId);
coloringStore.initConfig(viewIdVal, layerIdVal);
coloringStore.initConfig(viewIdVal, layerIdVal); // initConfig instead of resetColorPreset for layers
coloringStore.updateBlendConfig(viewIdVal, layerIdVal, {
opacity: 0.3,
});
});
}

export function useSegmentGroupConfigInitializer(
viewId: MaybeRef<string>,
segmentGroupId: MaybeRef<string>
) {
useLayerConfigInitializerForSegmentGroups(viewId, segmentGroupId);

const configStore = useSegmentGroupConfigStore();
const config = computed(() =>
configStore.getConfig(unref(viewId), unref(segmentGroupId))
);

watchImmediate(config, (config_) => {
if (config_) return;
const viewIdVal = unref(viewId);
const layerIdVal = unref(segmentGroupId);
configStore.initConfig(viewIdVal, layerIdVal);
});
}
Loading

0 comments on commit 0021740

Please sign in to comment.