Skip to content

Commit

Permalink
feat(segmentGroups): add per view outline opacity config
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulHax committed Oct 24, 2024
1 parent b1024b0 commit 13ea158
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 12 deletions.
26 changes: 25 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,24 @@ const setOpacity = (opacity: number) => {
},
});
};
const { config, updateConfig: updateSegmentGroupConfig } =
useGlobalSegmentGroupConfig(groupId);
const outlineOpacity = computed({
get: () => config.value!.config!.outlineOpacity,
set: (opacity: number) => {
updateSegmentGroupConfig({
outlineOpacity: opacity,
});
},
});
</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 +49,15 @@ 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"
/>
</template>
20 changes: 13 additions & 7 deletions 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 @@ -138,9 +139,14 @@ watchEffect(applySegmentColoring);
sliceRep.property.setUseLabelOutline(true);
sliceRep.property.setUseLookupTableScalarRange(true); // For the labelmap is rendered correctly
// watchEffect(() => {
// sliceRep.property.setLabelOutlineOpacity(opacity.value);
// });
const configStore = useSegmentGroupConfigStore();
const config = computed(() =>
configStore.getConfig(unref(viewId), unref(segmentationId))
);
watchEffect(() => {
sliceRep.property.setLabelOutlineOpacity(config.value?.outlineOpacity ?? 1);
});
const outlinePixelThickness = 2;
watchEffect(() => {
Expand All @@ -149,11 +155,11 @@ watchEffect(() => {
const { segments } = metadata.value;
const max = Math.max(...segments.order);
const segThicknesses = Array.from({ length: max }, (_, i) => {
const value = i + 1;
const segment = segments.byValue[value];
const segThicknesses = Array.from({ length: max }, (_, value) => {
const segment = segments.byValue[value + 1];
return ((!segment || segment.visible) && outlinePixelThickness) || 0;
});
// console.log('segThicknesses', segThicknesses);
sliceRep.property.setLabelOutlineThickness(segThicknesses);
});
Expand Down
26 changes: 23 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,25 @@ 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
});
}

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);
});
}
6 changes: 6 additions & 0 deletions src/io/state-file/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
SliceConfig,
WindowLevelConfig,
LayersConfig,
SegmentGroupConfig,
VolumeColorConfig,
} from '../../store/view-configs/types';
import type { LPSAxisDir, LPSAxis } from '../../types/lps';
Expand Down Expand Up @@ -215,10 +216,15 @@ const LayersConfig = z.object({
blendConfig: BlendConfig,
}) satisfies z.ZodType<LayersConfig>;

const SegmentGroupConfig = z.object({
outlineOpacity: z.number(),
}) satisfies z.ZodType<SegmentGroupConfig>;

const ViewConfig = z.object({
window: WindowLevelConfig.optional(),
slice: SliceConfig.optional(),
layers: LayersConfig.optional(),
segmentGroup: SegmentGroupConfig.optional(),
camera: CameraConfig.optional(),
volumeColorConfig: VolumeColorConfig.optional(),
});
Expand Down
4 changes: 3 additions & 1 deletion src/store/view-configs/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { StateFile, ViewConfig } from '../../io/state-file/schema';
import {
CameraConfig,
LayersConfig,
SegmentGroupConfig,
SliceConfig,
VolumeColorConfig,
WindowLevelConfig,
Expand All @@ -14,7 +15,8 @@ type SubViewConfig =
| SliceConfig
| VolumeColorConfig
| WindowLevelConfig
| LayersConfig;
| LayersConfig
| SegmentGroupConfig;

type ViewConfigGetter = (
viewID: string,
Expand Down
129 changes: 129 additions & 0 deletions src/store/view-configs/segmentGroups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { reactive, computed, unref, MaybeRef } from 'vue';
import { defineStore } from 'pinia';

import {
DoubleKeyRecord,
deleteSecondKey,
getDoubleKeyRecord,
patchDoubleKeyRecord,
} from '@/src/utils/doubleKeyRecord';
import { Maybe } from '@/src/types';

import { createViewConfigSerializer } from './common';
import { ViewConfig } from '../../io/state-file/schema';
import { SegmentGroupConfig } from './types';

type Config = SegmentGroupConfig;
const CONFIG_NAME = 'segmentGroup';

export const defaultConfig = () => ({
outlineOpacity: 1.0,
});

export const useSegmentGroupConfigStore = defineStore(
`${CONFIG_NAME}Config`,
() => {
const configs = reactive<DoubleKeyRecord<Config>>({});

const getConfig = (viewID: Maybe<string>, dataID: Maybe<string>) =>
getDoubleKeyRecord(configs, viewID, dataID);

const updateConfig = (
viewID: string,
dataID: string,
patch: Partial<Config>
) => {
const config = {
...defaultConfig(),
...getConfig(viewID, dataID),
...patch,
};

patchDoubleKeyRecord(configs, viewID, dataID, config);
};

const initConfig = (viewID: string, dataID: string) =>
updateConfig(viewID, dataID, defaultConfig());

const removeView = (viewID: string) => {
delete configs[viewID];
};

const removeData = (dataID: string, viewID?: string) => {
if (viewID) {
delete configs[viewID]?.[dataID];
} else {
deleteSecondKey(configs, dataID);
}
};

const serialize = createViewConfigSerializer(configs, CONFIG_NAME);

const deserialize = (
viewID: string,
config: Record<string, ViewConfig>
) => {
Object.entries(config).forEach(([dataID, viewConfig]) => {
if (viewConfig.segmentGroup) {
updateConfig(viewID, dataID, viewConfig.segmentGroup);
}
});
};

// For updating all configs together //

const aConfig = computed(() => {
const viewIDs = Object.keys(configs);
if (viewIDs.length === 0) return null;
const firstViewID = viewIDs[0];
const dataIDs = Object.keys(configs[firstViewID]);
if (dataIDs.length === 0) return null;
const firstDataID = dataIDs[0];
return configs[firstViewID][firstDataID];
});

const updateAllConfigs = (dataID: string, patch: Partial<Config>) => {
Object.keys(configs).forEach((viewID) => {
updateConfig(viewID, dataID, patch);
});
};

return {
configs,
getConfig,
initConfig,
updateConfig,
removeView,
removeData,
serialize,
deserialize,
aConfig,
updateAllConfigs,
};
}
);

export const useGlobalSegmentGroupConfig = (dataId: MaybeRef<string>) => {
const store = useSegmentGroupConfigStore();

const views = computed(() => Object.keys(store.configs));

const configs = computed(() =>
views.value.map((viewID) => ({
config: store.getConfig(viewID, unref(dataId)),
viewID,
}))
);

// get any one
const config = computed(() => configs.value.find(({ config: c }) => c));

// update all configs
const updateConfig = (patch: Partial<Config>) => {
configs.value.forEach(({ viewID }) =>
store.updateConfig(viewID, unref(dataId), patch)
);
};

return { config, updateConfig };
};
4 changes: 4 additions & 0 deletions src/store/view-configs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ export interface LayersConfig {
opacityFunction: OpacityFunction;
blendConfig: BlendConfig;
}

export interface SegmentGroupConfig {
outlineOpacity: number;
}

0 comments on commit 13ea158

Please sign in to comment.