Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/components/FillHolesParameterControls.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<template>
<div class="d-flex align-center">
<mini-expansion-panel>
<template #title>Fill enclosed holes in the segmentation.</template>
<ul>
<li>Finds background regions fully enclosed by segments on a slice.</li>
<li>
Fills holes on the current slice by default, or every slice of the
active view's axis.
</li>
<li>
All-segments mode fills each hole with the surrounding segment's
label; selected-segment mode fills only the active segment's holes.
</li>
</ul>
</mini-expansion-panel>
</div>

<div
v-for="scope in scopes"
:key="scope.label"
class="w-100 px-4 mb-4 d-flex flex-column align-center"
>
<div class="text-caption mb-1">{{ scope.label }}</div>
<v-btn-toggle
:model-value="scope.value"
density="compact"
variant="outlined"
divided
mandatory
:disabled="isDisabled"
@update:model-value="scope.onChange"
>
<v-btn
v-for="option in scope.options"
:key="option.value"
:value="option.value"
size="small"
>
{{ option.label }}
</v-btn>
</v-btn-toggle>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import {
useFillHolesStore,
FillHolesSliceScope,
FillHolesSegmentScope,
} from '@/src/store/tools/fillHoles';
import { usePaintProcessStore } from '@/src/store/tools/paintProcess';
import MiniExpansionPanel from './MiniExpansionPanel.vue';

const fillHolesStore = useFillHolesStore();
const processStore = usePaintProcessStore();

const isDisabled = computed(() => processStore.processStep === 'previewing');

type ScopeControl = {
label: string;
value: string;
onChange: (value: string) => void;
options: { value: string; label: string }[];
};

const scopes = computed<ScopeControl[]>(() => [
{
label: 'Slices',
value: fillHolesStore.sliceScope,
onChange: (value) =>
fillHolesStore.setSliceScope(value as FillHolesSliceScope),
options: [
{ value: FillHolesSliceScope.CurrentSlice, label: 'Current slice' },
{ value: FillHolesSliceScope.WholeVolume, label: 'All slices' },
],
},
{
label: 'Segments',
value: fillHolesStore.segmentScope,
onChange: (value) =>
fillHolesStore.setSegmentScope(value as FillHolesSegmentScope),
options: [
{ value: FillHolesSegmentScope.AllSegments, label: 'All segments' },
{
value: FillHolesSegmentScope.SelectedSegment,
label: 'Selected segment',
},
],
},
]);
</script>
35 changes: 13 additions & 22 deletions src/components/ProcessControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,28 @@
<div class="d-flex flex-column align-center w-100">
<ProcessTypeSelector />

<template v-if="activeProcessType === ProcessType.FillBetween">
<FillBetweenParameterControls />
<ProcessWorkflow :algorithm="fillBetweenAlgorithm" />
</template>

<template v-if="activeProcessType === ProcessType.GaussianSmooth">
<GaussianSmoothParameterControls />
<ProcessWorkflow :algorithm="gaussianSmoothAlgorithm" />
<template v-if="activeDefinition">
<component :is="activeDefinition.controls" />
<ProcessWorkflow
:algorithm="activeDefinition.getAlgorithm()"
:requires-active-segment="
activeDefinition.requiresActiveSegment?.() ?? true
"
/>
</template>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import {
ProcessType,
usePaintProcessStore,
} from '@/src/store/tools/paintProcess';
import { usePaintProcessStore } from '@/src/store/tools/paintProcess';
import ProcessTypeSelector from './ProcessTypeSelector.vue';
import ProcessWorkflow from './ProcessWorkflow.vue';
import FillBetweenParameterControls from './FillBetweenParameterControls.vue';
import GaussianSmoothParameterControls from './GaussianSmoothParameterControls.vue';
import { useFillBetweenStore } from '../store/tools/fillBetween';
import { useGaussianSmoothStore } from '../store/tools/gaussianSmooth';
import { PROCESS_DEFINITIONS } from './processes';

const processStore = usePaintProcessStore();
const fillBetweenStore = useFillBetweenStore();
const gaussianSmoothStore = useGaussianSmoothStore();

const activeProcessType = computed(() => processStore.activeProcessType);

const fillBetweenAlgorithm = fillBetweenStore.computeAlgorithm;
const gaussianSmoothAlgorithm = gaussianSmoothStore.computeAlgorithm;
const activeDefinition = computed(() =>
PROCESS_DEFINITIONS.find((def) => def.type === processStore.activeProcessType)
);
</script>
31 changes: 8 additions & 23 deletions src/components/ProcessTypeSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
v-model="activeProcessType"
mandatory
selected-class="selected"
class="d-flex align-center justify-center"
class="d-flex align-center justify-center flex-wrap"
>
<v-item
:value="ProcessType.FillBetween"
v-for="definition in PROCESS_DEFINITIONS"
:key="definition.type"
:value="definition.type"
v-slot="{ selectedClass, toggle }"
>
<v-btn
Expand All @@ -17,23 +19,8 @@
:class="['process-button', 'mx-2', selectedClass]"
@click.stop="toggle"
>
<v-icon>mdi-layers-triple</v-icon>
<span class="text-caption">Fill Between</span>
</v-btn>
</v-item>
<v-item
:value="ProcessType.GaussianSmooth"
v-slot="{ selectedClass, toggle }"
>
<v-btn
variant="tonal"
rounded="8"
stacked
:class="['process-button', 'mx-2', selectedClass]"
@click.stop="toggle"
>
<v-icon>mdi-blur</v-icon>
<span class="text-caption">Smooth</span>
<v-icon>{{ definition.icon }}</v-icon>
<span class="text-caption">{{ definition.label }}</span>
</v-btn>
</v-item>
</v-item-group>
Expand All @@ -42,10 +29,8 @@

<script setup lang="ts">
import { computed } from 'vue';
import {
ProcessType,
usePaintProcessStore,
} from '@/src/store/tools/paintProcess';
import { usePaintProcessStore } from '@/src/store/tools/paintProcess';
import { PROCESS_DEFINITIONS } from './processes';

const processStore = usePaintProcessStore();

Expand Down
21 changes: 9 additions & 12 deletions src/components/ProcessWorkflow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@
<v-btn-toggle
v-if="processStep === 'previewing'"
:model-value="showingOriginal ? 0 : 1"
@update:model-value="handleToggleChange"
mandatory
variant="outlined"
divided
density="compact"
>
<v-btn :value="0" size="small">
<v-btn :value="0" size="small" @click="processStore.togglePreview()">
<v-icon start size="small">mdi-eye-outline</v-icon>
Original
</v-btn>
<v-btn :value="1" size="small">
<v-btn :value="1" size="small" @click="processStore.togglePreview()">
<v-icon start size="small">mdi-eye-settings</v-icon>
Processed
</v-btn>
Expand Down Expand Up @@ -60,9 +59,12 @@ import {

interface Props {
algorithm: ProcessAlgorithm;
requiresActiveSegment?: boolean;
}

const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
requiresActiveSegment: true,
});

const processStore = usePaintProcessStore();
const paintStore = usePaintToolStore();
Expand All @@ -73,14 +75,9 @@ const showingOriginal = computed(() => processStore.showingOriginal);
function startCompute() {
const id = paintStore.activeSegmentGroupID;
if (!id) return;
processStore.startProcess(id, props.algorithm);
}

function handleToggleChange(value: number) {
const shouldShowOriginal = value === 0;
if (shouldShowOriginal !== showingOriginal.value) {
processStore.togglePreview();
}
processStore.startProcess(id, props.algorithm, {
requiresActiveSegment: props.requiresActiveSegment,
});
}

function handleCancel() {
Expand Down
56 changes: 56 additions & 0 deletions src/components/processes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Component } from 'vue';
import {
ProcessType,
type ProcessAlgorithm,
} from '@/src/store/tools/paintProcess';
import {
useFillHolesStore,
FillHolesSegmentScope,
} from '@/src/store/tools/fillHoles';
import { useFillBetweenStore } from '@/src/store/tools/fillBetween';
import { useGaussianSmoothStore } from '@/src/store/tools/gaussianSmooth';
import FillHolesParameterControls from './FillHolesParameterControls.vue';
import FillBetweenParameterControls from './FillBetweenParameterControls.vue';
import GaussianSmoothParameterControls from './GaussianSmoothParameterControls.vue';

export type ProcessDefinition = {
type: ProcessType;
label: string;
icon: string;
controls: Component;
// Resolved lazily so each call reads the live store (Pinia singletons).
getAlgorithm: () => ProcessAlgorithm;
// Whether the process needs the active segment. Defaults to true; processes
// that act on every segment opt out. Resolved lazily for reactivity.
requiresActiveSegment?: () => boolean;
};

// Single source of truth for the paint processes. Adding a process is one
// entry here instead of synchronized edits across the selector, the controls,
// and the type enum.
export const PROCESS_DEFINITIONS: ProcessDefinition[] = [
{
type: ProcessType.FillHoles,
label: 'Fill Holes',
icon: 'mdi-format-color-fill',
controls: FillHolesParameterControls,
getAlgorithm: () => useFillHolesStore().computeAlgorithm,
requiresActiveSegment: () =>
useFillHolesStore().segmentScope ===
FillHolesSegmentScope.SelectedSegment,
},
{
type: ProcessType.FillBetween,
label: 'Fill Between',
icon: 'mdi-layers-triple',
controls: FillBetweenParameterControls,
getAlgorithm: () => useFillBetweenStore().computeAlgorithm,
},
{
type: ProcessType.GaussianSmooth,
label: 'Smooth',
icon: 'mdi-blur',
controls: GaussianSmoothParameterControls,
getAlgorithm: () => useGaussianSmoothStore().computeAlgorithm,
},
];
Loading
Loading