From 5675ca4acc7516a01090ffad5f4bdf9314bb7baf Mon Sep 17 00:00:00 2001 From: Paul Elliott Date: Thu, 26 Sep 2024 16:31:05 -0400 Subject: [PATCH] feat(segment-groups): add opacity slider per segment group --- src/components/PaintControls.vue | 42 +------------- src/components/SegmentGroupControls.vue | 5 ++ src/components/SegmentGroupOpacity.vue | 55 +++++++++++++++++++ .../vtk/VtkLayerSliceRepresentation.vue | 4 +- .../VtkSegmentationSliceRepresentation.vue | 17 ++++-- src/composables/useLayerConfigInitializer.ts | 3 +- .../useSegmentGroupConfigInitializer.ts | 21 +++++++ src/io/state-file/index.ts | 1 - src/io/state-file/schema.ts | 2 +- src/store/datasets-layers.ts | 11 ++-- src/store/tools/paint.ts | 9 --- src/store/view-configs/layers.ts | 10 ++-- 12 files changed, 107 insertions(+), 73 deletions(-) create mode 100644 src/components/SegmentGroupOpacity.vue create mode 100644 src/composables/useSegmentGroupConfigInitializer.ts diff --git a/src/components/PaintControls.vue b/src/components/PaintControls.vue index 070917e75..a4ecf5f8d 100644 --- a/src/components/PaintControls.vue +++ b/src/components/PaintControls.vue @@ -59,36 +59,6 @@ - - - - - - - @@ -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) => { @@ -127,8 +89,6 @@ export default defineComponent({ return { brushSize, setBrushSize, - opacity, - setOpacity, mode, PaintMode, }; diff --git a/src/components/SegmentGroupControls.vue b/src/components/SegmentGroupControls.vue index 8b18f3daa..913e82e77 100644 --- a/src/components/SegmentGroupControls.vue +++ b/src/components/SegmentGroupControls.vue @@ -1,4 +1,5 @@ + + diff --git a/src/components/vtk/VtkLayerSliceRepresentation.vue b/src/components/vtk/VtkLayerSliceRepresentation.vue index 0eb6ce844..553c7951e 100644 --- a/src/components/vtk/VtkLayerSliceRepresentation.vue +++ b/src/components/vtk/VtkLayerSliceRepresentation.vue @@ -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; } diff --git a/src/components/vtk/VtkSegmentationSliceRepresentation.vue b/src/components/vtk/VtkSegmentationSliceRepresentation.vue index 2f63dfeab..9d71df31f 100644 --- a/src/components/vtk/VtkSegmentationSliceRepresentation.vue +++ b/src/components/vtk/VtkSegmentationSliceRepresentation.vue @@ -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; @@ -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 diff --git a/src/composables/useLayerConfigInitializer.ts b/src/composables/useLayerConfigInitializer.ts index 08bb84b38..4d0545641 100644 --- a/src/composables/useLayerConfigInitializer.ts +++ b/src/composables/useLayerConfigInitializer.ts @@ -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, - layerId: MaybeRef + layerId: MaybeRef ) { const coloringStore = useLayerColoringStore(); const colorConfig = computed(() => diff --git a/src/composables/useSegmentGroupConfigInitializer.ts b/src/composables/useSegmentGroupConfigInitializer.ts new file mode 100644 index 000000000..6a350b64f --- /dev/null +++ b/src/composables/useSegmentGroupConfigInitializer.ts @@ -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, + layerId: MaybeRef +) { + 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, {}); + }); +} diff --git a/src/io/state-file/index.ts b/src/io/state-file/index.ts index 8a7bcd05d..c2d3c5402 100644 --- a/src/io/state-file/index.ts +++ b/src/io/state-file/index.ts @@ -35,7 +35,6 @@ export async function serialize() { activeSegmentGroupID: null, activeSegment: null, brushSize: 8, - labelmapOpacity: 1, }, crop: {}, current: Tools.WindowLevel, diff --git a/src/io/state-file/schema.ts b/src/io/state-file/schema.ts index 65a8cbc90..87f1a09cd 100644 --- a/src/io/state-file/schema.ts +++ b/src/io/state-file/schema.ts @@ -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({ diff --git a/src/store/datasets-layers.ts b/src/store/datasets-layers.ts index 75b4b33a3..cbad3f317 100644 --- a/src/store/datasets-layers.ts +++ b/src/store/datasets-layers.ts @@ -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; const parentToLayers = ref>({}); - const layerImages = ref>({}); + const layerImages = ref>({}); async function _addLayer( this: _This, @@ -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, @@ -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() diff --git a/src/store/tools/paint.ts b/src/store/tools/paint.ts index 0a96dffcd..e640201d0 100644 --- a/src/store/tools/paint.ts +++ b/src/store/tools/paint.ts @@ -19,7 +19,6 @@ export const usePaintToolStore = defineStore('paint', () => { const activeSegment = ref>(null); const brushSize = ref(DEFAULT_BRUSH_SIZE); const strokePoints = ref([]); - const labelmapOpacity = ref(1); const isActive = ref(false); const { currentImageID } = useCurrentImage(); @@ -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; @@ -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( @@ -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) { @@ -221,7 +214,6 @@ export const usePaintToolStore = defineStore('paint', () => { activeSegment, brushSize, strokePoints, - labelmapOpacity, isActive, getWidgetFactory, @@ -234,7 +226,6 @@ export const usePaintToolStore = defineStore('paint', () => { setActiveLabelmapFromImage, setActiveSegment, setBrushSize, - setLabelmapOpacity, setSliceAxis, startStroke, placeStrokePoint, diff --git a/src/store/view-configs/layers.ts b/src/store/view-configs/layers.ts index 66507e2bd..74a38f8aa 100644 --- a/src/store/view-configs/layers.ts +++ b/src/store/view-configs/layers.ts @@ -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) { @@ -84,7 +84,7 @@ export const useLayerColoringStore = defineStore('layerColoring', () => { ) => { return ( viewID: string, - dataID: LayerID, + dataID: string, update: Partial ) => { const config = getConfig(viewID, dataID) ?? defaultLayersConfig(); @@ -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; @@ -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)); };