Skip to content

Commit

Permalink
Merge pull request #654 from PaulHax/seg-group-trans
Browse files Browse the repository at this point in the history
Segment Groups: opacity slider and visibility toggle button
  • Loading branch information
floryst authored Oct 2, 2024
2 parents f78981a + 74c9998 commit 8ebf62e
Show file tree
Hide file tree
Showing 18 changed files with 195 additions and 99 deletions.
15 changes: 8 additions & 7 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 @@ -31,7 +31,7 @@
"@kitware/vtk.js": "^29.0.0",
"@netlify/edge-functions": "^2.0.0",
"@sentry/vue": "^7.54.0",
"@velipso/polybool": "^1.1.0",
"@velipso/polybool": "^2.0.11",
"@vueuse/core": "^10.7.0",
"core-js": "3.22.5",
"deep-equal": "^2.0.5",
Expand Down
12 changes: 0 additions & 12 deletions patches/@velipso+polybool+1.1.0.patch

This file was deleted.

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
31 changes: 31 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 All @@ -10,6 +11,7 @@ import {
DataSelection,
} from '@/src/utils/dataSelection';
import { useSegmentGroupStore } from '@/src/store/segmentGroups';
import { useGlobalLayerColorConfig } from '@/src/composables/useGlobalLayerColorConfig';
import { usePaintToolStore } from '@/src/store/tools/paint';
import { Maybe } from '@/src/types';
import { reactive, ref, computed, watch, toRaw } from 'vue';
Expand All @@ -25,9 +27,20 @@ const currentSegmentGroups = computed(() => {
const { orderByParent, metadataByID } = segmentGroupStore;
if (!(currentImageID.value in orderByParent)) return [];
return orderByParent[currentImageID.value].map((id) => {
const { sampledConfig, updateConfig } = useGlobalLayerColorConfig(id);
return {
id,
name: metadataByID[id].name,
visibility: sampledConfig.value?.config?.blendConfig.visibility ?? true,
toggleVisibility: () => {
const currentBlend = sampledConfig.value!.config!.blendConfig;
updateConfig({
blendConfig: {
...currentBlend,
visibility: !currentBlend.visibility,
},
});
},
};
});
});
Expand Down Expand Up @@ -206,6 +219,20 @@ function openSaveDialog(id: string) {
<div class="d-flex flex-row align-center w-100" :title="group.name">
<span class="group-name">{{ group.name }}</span>
<v-spacer />
<v-btn
icon
variant="flat"
size="small"
@click.stop="group.toggleVisibility"
>
<v-icon v-if="group.visibility" style="pointer-events: none"
>mdi-eye</v-icon
>
<v-icon v-else style="pointer-events: none">mdi-eye-off</v-icon>
<v-tooltip location="left" activator="parent">{{
group.visibility ? 'Hide' : 'Show'
}}</v-tooltip>
</v-btn>
<v-btn
icon="mdi-content-save"
size="small"
Expand All @@ -231,6 +258,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
39 changes: 39 additions & 0 deletions src/components/SegmentGroupOpacity.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup lang="ts">
import { computed, toRefs } from 'vue';
import { useGlobalLayerColorConfig } from '@/src/composables/useGlobalLayerColorConfig';
const props = defineProps<{
groupId: string;
}>();
const { groupId } = toRefs(props);
const { sampledConfig, updateConfig } = useGlobalLayerColorConfig(groupId);
const blendConfig = computed(() => sampledConfig.value!.config!.blendConfig);
const setOpacity = (opacity: number) => {
updateConfig({
blendConfig: {
...blendConfig.value,
// 1.0 puts us in Opaque render pass which changes stack order.
opacity: Math.min(opacity, 0.9999),
},
});
};
</script>

<template>
<v-slider
class="pa-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="setOpacity($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
27 changes: 22 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,27 @@ 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);
const coloringStore = useLayerColoringStore();
// visibility
const visibility = computed(
() =>
coloringStore.getConfig(viewId.value, segmentationId.value)!.blendConfig
.visibility
);
watchEffect(() => {
sliceRep.actor.setVisibility(visibility.value);
});
// opacity
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
35 changes: 35 additions & 0 deletions src/composables/useGlobalLayerColorConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { computed, MaybeRef, unref } from 'vue';
import { InitViewSpecs } from '@/src/config';
import useLayerColoringStore from '@/src/store/view-configs/layers';
import { LayersConfig } from '@/src/store/view-configs/types';
import { useSegmentGroupConfigInitializer } from '@/src/composables/useSegmentGroupConfigInitializer';

// Returns first existing view's config as the "value" and updates all views' configs with updateConfig()
export const useGlobalLayerColorConfig = (layerId: MaybeRef<string>) => {
const layerColoringStore = useLayerColoringStore();

const VIEWS_2D = Object.entries(InitViewSpecs)
.filter(([, { viewType }]) => viewType === '2D')
.map(([viewID]) => viewID);

useSegmentGroupConfigInitializer(VIEWS_2D[0], unref(layerId));

const layerConfigs = computed(() =>
VIEWS_2D.map((viewID) => ({
config: layerColoringStore.getConfig(viewID, unref(layerId)),
viewID,
}))
);

const sampledConfig = computed(() =>
layerConfigs.value.find(({ config }) => config)
);

const updateConfig = (patch: Partial<LayersConfig>) => {
layerConfigs.value.forEach(({ viewID }) =>
layerColoringStore.updateConfig(viewID, unref(layerId), patch)
);
};

return { sampledConfig, updateConfig };
};
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.initConfig(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
3 changes: 2 additions & 1 deletion src/io/state-file/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ const VolumeColorConfig = z.object({

const BlendConfig = z.object({
opacity: z.number(),
visibility: z.boolean(),
}) satisfies z.ZodType<BlendConfig>;

const LayersConfig = z.object({
Expand Down Expand Up @@ -314,7 +315,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
Loading

0 comments on commit 8ebf62e

Please sign in to comment.