Skip to content

Commit

Permalink
feat(segment-groups): add opacity slider per segment group
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulHax committed Sep 26, 2024
1 parent f78981a commit 5675ca4
Show file tree
Hide file tree
Showing 12 changed files with 107 additions and 73 deletions.
42 changes: 1 addition & 41 deletions src/components/PaintControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,6 @@
</v-slider>
</v-col>
</v-row>
<v-row no-gutters align="center">
<v-col>
<v-slider
:model-value="opacity"
@update:model-value="setOpacity"
density="compact"
hide-details
label="Opacity"
min="0"
max="1"
step="0.01"
>
<template v-slot:append>
<v-text-field
:model-value="opacity"
@input="setOpacity"
variant="plain"
class="mt-n3 pt-0 pl-2"
style="width: 60px"
density="compact"
hide-details
type="number"
min="0"
max="1"
step="0.1"
/>
</template>
</v-slider>
</v-col>
</v-row>
</v-container>
</template>

Expand All @@ -103,20 +73,12 @@ export default defineComponent({
setup() {
const paintStore = usePaintToolStore();
const {
brushSize,
labelmapOpacity: opacity,
activeMode,
} = storeToRefs(paintStore);
const { brushSize, activeMode } = storeToRefs(paintStore);
const setBrushSize = (size: number) => {
paintStore.setBrushSize(Number(size));
};
const setOpacity = (op: number) => {
paintStore.setLabelmapOpacity(Number(op));
};
const mode = computed({
get: () => activeMode.value,
set: (m) => {
Expand All @@ -127,8 +89,6 @@ export default defineComponent({
return {
brushSize,
setBrushSize,
opacity,
setOpacity,
mode,
PaintMode,
};
Expand Down
5 changes: 5 additions & 0 deletions src/components/SegmentGroupControls.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import SegmentGroupOpacity from '@/src/components/SegmentGroupOpacity.vue';
import SegmentList from '@/src/components/SegmentList.vue';
import CloseableDialog from '@/src/components/CloseableDialog.vue';
import SaveSegmentGroupDialog from '@/src/components/SaveSegmentGroupDialog.vue';
Expand Down Expand Up @@ -231,6 +232,10 @@ function openSaveDialog(id: string) {
<v-divider />
</div>
<div v-else class="text-center text-caption">No selected image</div>
<segment-group-opacity
v-if="currentSegmentGroupID"
:group-id="currentSegmentGroupID"
/>
<segment-list
v-if="currentSegmentGroupID"
:group-id="currentSegmentGroupID"
Expand Down
55 changes: 55 additions & 0 deletions src/components/SegmentGroupOpacity.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script setup lang="ts">
import { computed, toRefs } from 'vue';
import { BlendConfig } from '@/src/types/views';
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { useSegmentGroupConfigInitializer } from '@/src/composables/useSegmentGroupConfigInitializer';
import { InitViewSpecs } from '../config';
const props = defineProps<{
groupId: string;
}>();
const { groupId } = toRefs(props);
const layerColoringStore = useLayerColoringStore();
const VIEWS_2D = Object.entries(InitViewSpecs)
.filter(([, { viewType }]) => viewType === '2D')
.map(([viewID]) => viewID);
useSegmentGroupConfigInitializer(VIEWS_2D[0], groupId.value);
const layerConfigs = computed(() =>
VIEWS_2D.map((viewID) => ({
config: layerColoringStore.getConfig(viewID, groupId.value),
viewID,
}))
);
const blendConfig = computed(
() => layerConfigs.value.find(({ config }) => config)!.config!.blendConfig
);
const setBlendConfig = (key: keyof BlendConfig, value: any) => {
layerConfigs.value.forEach(({ viewID }) =>
layerColoringStore.updateBlendConfig(viewID, groupId.value, {
[key]: value,
})
);
};
</script>

<template>
<v-slider
class="py-4"
label="Segment Group Opacity"
min="0"
max="1"
step="0.01"
density="compact"
hide-details
thumb-label
:model-value="blendConfig.opacity"
@update:model-value="setBlendConfig('opacity', $event)"
/>
</template>
4 changes: 2 additions & 2 deletions src/components/vtk/VtkLayerSliceRepresentation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunc
import { vtkFieldRef } from '@/src/core/vtk/vtkFieldRef';
import { syncRef } from '@vueuse/core';
import { useSliceConfig } from '@/src/composables/useSliceConfig';
import { LayerID, useLayersStore } from '@/src/store/datasets-layers';
import { useLayersStore } from '@/src/store/datasets-layers';
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { useLayerConfigInitializer } from '@/src/composables/useLayerConfigInitializer';
import { applyColoring } from '@/src/composables/useColoringEffect';
interface Props {
viewId: string;
layerId: LayerID;
layerId: string;
parentId: string;
axis: LPSAxis;
}
Expand Down
17 changes: 12 additions & 5 deletions src/components/vtk/VtkSegmentationSliceRepresentation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunc
import { vtkFieldRef } from '@/src/core/vtk/vtkFieldRef';
import { syncRef } from '@vueuse/core';
import { useSliceConfig } from '@/src/composables/useSliceConfig';
import { usePaintToolStore } from '@/src/store/tools/paint';
import { storeToRefs } from 'pinia';
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { useSegmentGroupConfigInitializer } from '@/src/composables/useSegmentGroupConfigInitializer';
interface Props {
viewId: string;
Expand Down Expand Up @@ -60,10 +60,17 @@ sliceRep.property.setUseLookupTableScalarRange(true);
sliceRep.mapper.setResolveCoincidentTopologyToPolygonOffset();
sliceRep.mapper.setResolveCoincidentTopologyPolygonOffsetParameters(-2, -2);
// set opacity from painting tool
const { labelmapOpacity: sliceOpacity } = storeToRefs(usePaintToolStore());
useSegmentGroupConfigInitializer(viewId.value, segmentationId.value);
// opacity
const coloringStore = useLayerColoringStore();
const opacity = computed(
() =>
coloringStore.getConfig(viewId.value, segmentationId.value)?.blendConfig
.opacity
);
watchEffect(() => {
sliceRep.property.setOpacity(sliceOpacity.value);
sliceRep.property.setOpacity(opacity.value!);
});
// set slicing mode
Expand Down
3 changes: 1 addition & 2 deletions src/composables/useLayerConfigInitializer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { LayerID } from '@/src/store/datasets-layers';
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { watchImmediate } from '@vueuse/core';
import { MaybeRef, computed, unref } from 'vue';

export function useLayerConfigInitializer(
viewId: MaybeRef<string>,
layerId: MaybeRef<LayerID>
layerId: MaybeRef<string>
) {
const coloringStore = useLayerColoringStore();
const colorConfig = computed(() =>
Expand Down
21 changes: 21 additions & 0 deletions src/composables/useSegmentGroupConfigInitializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { watchImmediate } from '@vueuse/core';
import { MaybeRef, computed, unref } from 'vue';

export function useSegmentGroupConfigInitializer(
viewId: MaybeRef<string>,
layerId: MaybeRef<string>
) {
const coloringStore = useLayerColoringStore();
const colorConfig = computed(() =>
coloringStore.getConfig(unref(viewId), unref(layerId))
);

watchImmediate(colorConfig, (config) => {
if (config) return;

const viewIdVal = unref(viewId);
const layerIdVal = unref(layerId);
coloringStore.updateConfig(viewIdVal, layerIdVal, {});
});
}
1 change: 0 additions & 1 deletion src/io/state-file/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export async function serialize() {
activeSegmentGroupID: null,
activeSegment: null,
brushSize: 8,
labelmapOpacity: 1,
},
crop: {},
current: Tools.WindowLevel,
Expand Down
2 changes: 1 addition & 1 deletion src/io/state-file/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ const Paint = z.object({
activeSegmentGroupID: z.string().nullable(),
activeSegment: z.number().nullish(),
brushSize: z.number(),
labelmapOpacity: z.number(),
labelmapOpacity: z.number().optional(), // labelmapOpacity now ignored. Opacity per segment group via layerColoring store.
});

const LPSCroppingPlanes = z.object({
Expand Down
11 changes: 4 additions & 7 deletions src/store/datasets-layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ import { ensureSameSpace } from '@/src/io/resample/resample';
import { useErrorMessage } from '../composables/useErrorMessage';
import { Manifest, StateFile } from '../io/state-file/schema';

// differ from Image/Volume IDs with a branded type
export type LayerID = string & { __type: 'LayerID' };

export type Layer = {
selection: DataSelection;
id: LayerID;
id: string;
};

export const useLayersStore = defineStore('layer', () => {
type _This = ReturnType<typeof useLayersStore>;

const parentToLayers = ref<Record<string, Layer[]>>({});
const layerImages = ref<Record<LayerID, vtkImageData>>({});
const layerImages = ref<Record<string, vtkImageData>>({});

async function _addLayer(
this: _This,
Expand All @@ -31,7 +28,7 @@ export const useLayersStore = defineStore('layer', () => {
throw new Error('Tried to addLayer without parent data selection');
}

const id = `${parent}::${source}` as LayerID;
const id = `${parent}::${source}` as string;
this.parentToLayers[parent] = [
...(this.parentToLayers[parent] ?? []),
{ selection: source, id } as Layer,
Expand Down Expand Up @@ -105,7 +102,7 @@ export const useLayersStore = defineStore('layer', () => {
return parentToLayers.value[key] ?? [];
}

const getLayer = (layerID: LayerID) =>
const getLayer = (layerID: string) =>
Object.values(parentToLayers.value)
.flat()
.flat()
Expand Down
9 changes: 0 additions & 9 deletions src/store/tools/paint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const usePaintToolStore = defineStore('paint', () => {
const activeSegment = ref<Maybe<number>>(null);
const brushSize = ref(DEFAULT_BRUSH_SIZE);
const strokePoints = ref<vec3[]>([]);
const labelmapOpacity = ref(1);
const isActive = ref(false);

const { currentImageID } = useCurrentImage();
Expand Down Expand Up @@ -106,10 +105,6 @@ export const usePaintToolStore = defineStore('paint', () => {
this.$paint.setBrushSize(size);
}

function setLabelmapOpacity(opacity: number) {
labelmapOpacity.value = Math.min(1, Math.max(0, opacity));
}

function doPaintStroke(this: _This, axisIndex: 0 | 1 | 2) {
if (!activeLabelmap.value) {
return;
Expand Down Expand Up @@ -181,7 +176,6 @@ export const usePaintToolStore = defineStore('paint', () => {
paint.activeSegmentGroupID = activeSegmentGroupID.value ?? null;
paint.brushSize = brushSize.value;
paint.activeSegment = activeSegment.value;
paint.labelmapOpacity = labelmapOpacity.value;
}

function deserialize(
Expand All @@ -191,7 +185,6 @@ export const usePaintToolStore = defineStore('paint', () => {
) {
const { paint } = manifest.tools;
setBrushSize.call(this, paint.brushSize);
setLabelmapOpacity.call(this, paint.labelmapOpacity);
isActive.value = manifest.tools.current === Tools.Paint;

if (paint.activeSegmentGroupID !== null) {
Expand Down Expand Up @@ -221,7 +214,6 @@ export const usePaintToolStore = defineStore('paint', () => {
activeSegment,
brushSize,
strokePoints,
labelmapOpacity,
isActive,

getWidgetFactory,
Expand All @@ -234,7 +226,6 @@ export const usePaintToolStore = defineStore('paint', () => {
setActiveLabelmapFromImage,
setActiveSegment,
setBrushSize,
setLabelmapOpacity,
setSliceAxis,
startStroke,
placeStrokePoint,
Expand Down
10 changes: 5 additions & 5 deletions src/store/view-configs/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { isDicomImage } from '@/src/utils/dataSelection';
import { createViewConfigSerializer } from './common';
import { ViewConfig } from '../../io/state-file/schema';
import { LayersConfig } from './types';
import { LayerID, useLayersStore } from '../datasets-layers';
import { useLayersStore } from '../datasets-layers';
import { useDICOMStore } from '../datasets-dicom';

function getPreset(id: LayerID) {
function getPreset(id: string) {
const layersStore = useLayersStore();
const layer = layersStore.getLayer(id);
if (!layer) {
Expand Down Expand Up @@ -84,7 +84,7 @@ export const useLayerColoringStore = defineStore('layerColoring', () => {
) => {
return (
viewID: string,
dataID: LayerID,
dataID: string,
update: Partial<LayersConfig[K]>
) => {
const config = getConfig(viewID, dataID) ?? defaultLayersConfig();
Expand All @@ -101,7 +101,7 @@ export const useLayerColoringStore = defineStore('layerColoring', () => {
const updateOpacityFunction = createUpdateFunc('opacityFunction');
const updateBlendConfig = createUpdateFunc('blendConfig');

const setColorPreset = (viewID: string, layerID: LayerID, preset: string) => {
const setColorPreset = (viewID: string, layerID: string, preset: string) => {
const layersStore = useLayersStore();
const image = layersStore.layerImages[layerID];
if (!image) return;
Expand All @@ -119,7 +119,7 @@ export const useLayerColoringStore = defineStore('layerColoring', () => {
updateOpacityFunction(viewID, layerID, opFunc);
};

const resetColorPreset = (viewID: string, layerID: LayerID) => {
const resetColorPreset = (viewID: string, layerID: string) => {
setColorPreset(viewID, layerID, getPreset(layerID));
};

Expand Down

0 comments on commit 5675ca4

Please sign in to comment.