diff --git a/src/components/EditableChipList.vue b/src/components/EditableChipList.vue
index 3411863a..59786107 100644
--- a/src/components/EditableChipList.vue
+++ b/src/components/EditableChipList.vue
@@ -26,7 +26,7 @@ const props = withDefaults(
const itemsToRender = computed(() =>
props.items.map((item) => ({
key: item[props.itemKey] as string | number | symbol,
- title: item[props.itemTitle],
+ title: item[props.itemTitle] as string | undefined,
}))
);
@@ -47,7 +47,11 @@ const itemsToRender = computed(() =>
@click="toggle"
>
- {{ title }}
+
+
+ {{ title }}
+
+
diff --git a/src/components/SegmentGroupControls.vue b/src/components/SegmentGroupControls.vue
index 8f1f0dd9..fd8604ef 100644
--- a/src/components/SegmentGroupControls.vue
+++ b/src/components/SegmentGroupControls.vue
@@ -48,7 +48,7 @@ const currentSegmentGroups = computed(() => {
const paintStore = usePaintToolStore();
const currentSegmentGroupID = computed({
get: () => paintStore.activeSegmentGroupID,
- set: (id) => paintStore.setActiveLabelmap(id),
+ set: (id) => paintStore.setActiveSegmentGroup(id),
});
// clear selection if we delete the active segment group
@@ -204,6 +204,11 @@ function openSaveDialog(id: string) {
+
+
No selected image
-
{
{
+ if (!selectedSegment.value) return null;
+ return segmentGroupStore.getSegment(groupId.value, selectedSegment.value);
+});
+
+const segmentOpacity = computed(() => {
+ if (!selectedSegmentMask.value) return 1;
+ return selectedSegmentMask.value.color[3] / 255;
+});
+
+const setSegmentOpacity = (opacity: number) => {
+ if (!selectedSegmentMask.value) {
+ return;
+ }
+
+ const color = selectedSegmentMask.value.color;
+ segmentGroupStore.updateSegment(
+ groupId.value,
+ selectedSegmentMask.value.value,
+ {
+ color: [
+ ...(color.slice(0, 3) as [number, number, number]),
+ Math.round(opacity * 255),
+ ],
+ }
+ );
+};
+
+const toggleVisible = (value: number) => {
+ const segment = segmentGroupStore.getSegment(groupId.value, value);
+ if (!segment) return;
+ segmentGroupStore.updateSegment(groupId.value, value, {
+ visible: !segment.visible,
+ });
+};
+
+const allVisible = computed(() => {
+ return segments.value.every((seg) => seg.visible);
+});
+
+function toggleGlobalVisible() {
+ const visible = !allVisible.value;
+
+ segments.value.forEach((seg) => {
+ segmentGroupStore.updateSegment(groupId.value, seg.value, {
+ visible,
+ });
+ });
+}
+
// --- editing state --- //
const editingSegmentValue = ref>(null);
@@ -106,6 +159,30 @@ function deleteEditingSegment() {
+
+ Toggle Segments
+
+ mdi-eye
+ mdi-eye-off
+ {{
+ allVisible ? 'Hide' : 'Show'
+ }}
+
+
+
+
+
-
+
+
+ mdi-eye
+ mdi-eye-off
+ {{
+ item.visible ? 'Hide' : 'Show'
+ }}
+
{
const r = segment.color[0] || 0;
const g = segment.color[1] || 0;
const b = segment.color[2] || 0;
- const a = segment.color[3] || 0;
+ const a = (segment.visible && segment.color[3]) || 0;
cfun.addRGBPoint(segment.value, r / 255, g / 255, b / 255);
ofun.addPoint(segment.value, a / 255);
diff --git a/src/config.ts b/src/config.ts
index f2cc18ab..420f4948 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -178,6 +178,7 @@ export const Layouts: Record = [
].reduce((layouts, layout) => {
return { ...layouts, [layout.name]: layout };
}, {});
+
export const SAMPLE_DATA: SampleDataset[] = [
{
name: 'CTA Head and Neck',
diff --git a/src/io/state-file/schema.ts b/src/io/state-file/schema.ts
index b4abb7af..e251f7ac 100644
--- a/src/io/state-file/schema.ts
+++ b/src/io/state-file/schema.ts
@@ -240,6 +240,7 @@ const SegmentMask = z.object({
value: z.number(),
name: z.string(),
color: RGBAColor,
+ visible: z.boolean().default(true),
});
export const SegmentGroupMetadata = z.object({
diff --git a/src/store/segmentGroups.ts b/src/store/segmentGroups.ts
index f71193e0..4b7ded20 100644
--- a/src/store/segmentGroups.ts
+++ b/src/store/segmentGroups.ts
@@ -223,6 +223,7 @@ export const useSegmentGroupStore = defineStore('segmentGroup', () => {
value: segment.labelID,
name: segment.SegmentLabel,
color: [...segment.recommendedDisplayRGBValue, 255],
+ visible: true,
}));
}
}
@@ -237,6 +238,7 @@ export const useSegmentGroupStore = defineStore('segmentGroup', () => {
value,
name: makeDefaultSegmentName(value),
color: getNextColor(),
+ visible: true,
}));
}
@@ -327,6 +329,7 @@ export const useSegmentGroupStore = defineStore('segmentGroup', () => {
name: makeDefaultSegmentName(value),
value,
color: DEFAULT_SEGMENT_COLOR,
+ visible: true,
};
}
diff --git a/src/store/tools/paint.ts b/src/store/tools/paint.ts
index e640201d..6fc74b7d 100644
--- a/src/store/tools/paint.ts
+++ b/src/store/tools/paint.ts
@@ -1,7 +1,7 @@
import type { Vector2 } from '@kitware/vtk.js/types';
import { useCurrentImage } from '@/src/composables/useCurrentImage';
import { Manifest, StateFile } from '@/src/io/state-file/schema';
-import { computed, ref, watch } from 'vue';
+import { computed, ref, watchEffect } from 'vue';
import { vec3 } from 'gl-matrix';
import { defineStore } from 'pinia';
import { Maybe } from '@/src/types';
@@ -27,9 +27,10 @@ export const usePaintToolStore = defineStore('paint', () => {
return this.$paint.factory;
}
+ const segmentGroupStore = useSegmentGroupStore();
+
const activeLabelmap = computed(() => {
if (!activeSegmentGroupID.value) return null;
- const segmentGroupStore = useSegmentGroupStore();
return segmentGroupStore.dataIndex[activeSegmentGroupID.value] ?? null;
});
@@ -47,25 +48,37 @@ export const usePaintToolStore = defineStore('paint', () => {
/**
* Sets the active labelmap.
*/
- function setActiveLabelmap(segmentGroupID: Maybe) {
+ function setActiveSegmentGroup(segmentGroupID: Maybe) {
activeSegmentGroupID.value = segmentGroupID;
}
+ /**
+ * Gets the first segment group ID for a given image.
+ * @param imageID
+ */
+ function getFirstSegmentGroupID(imageID: Maybe): Maybe {
+ if (!imageID) return null;
+ const segmentGroups = segmentGroupStore.orderByParent[imageID];
+ if (segmentGroups && segmentGroups.length > 0) {
+ return segmentGroups[0];
+ }
+ return null;
+ }
+
/**
* Sets the active labelmap from a given image.
*
* If a labelmap exists, pick the first one. If no labelmap exists, create one.
*/
- function setActiveLabelmapFromImage(imageID: Maybe) {
+ function ensureActiveSegmentGroupForImage(imageID: Maybe) {
if (!imageID) {
- setActiveLabelmap(null);
+ setActiveSegmentGroup(null);
return;
}
- const segmentGroupStore = useSegmentGroupStore();
- const labelmaps = segmentGroupStore.orderByParent[imageID];
- if (labelmaps?.length) {
- activeSegmentGroupID.value = labelmaps[0];
+ const segmentGroupID = getFirstSegmentGroupID(imageID);
+ if (segmentGroupID) {
+ setActiveSegmentGroup(segmentGroupID);
} else {
activeSegmentGroupID.value =
segmentGroupStore.newLabelmapFromImage(imageID);
@@ -83,7 +96,6 @@ export const usePaintToolStore = defineStore('paint', () => {
if (!activeSegmentGroupID.value)
throw new Error('Cannot set active segment without a labelmap');
- const segmentGroupStore = useSegmentGroupStore();
const { segments } =
segmentGroupStore.metadataByID[activeSegmentGroupID.value];
@@ -158,7 +170,7 @@ export const usePaintToolStore = defineStore('paint', () => {
if (!imageID) {
return false;
}
- setActiveLabelmapFromImage(imageID);
+ ensureActiveSegmentGroupForImage(imageID);
this.$paint.setBrushSize(this.brushSize);
isActive.value = true;
@@ -190,22 +202,26 @@ export const usePaintToolStore = defineStore('paint', () => {
if (paint.activeSegmentGroupID !== null) {
activeSegmentGroupID.value =
segmentGroupIDMap[paint.activeSegmentGroupID];
- setActiveLabelmap(activeSegmentGroupID.value);
+ setActiveSegmentGroup(activeSegmentGroupID.value);
setActiveSegment.call(this, paint.activeSegment);
}
}
- // --- change labelmap if paint is active --- //
+ // Create segment group if paint is active and none exist.
+ // If paint is not active, but there is a segment group for the current image, set it as active.
+ watchEffect(() => {
+ const imageID = currentImageID.value;
+ if (!imageID) return;
- watch(
- currentImageID,
- (imageID) => {
- if (isActive.value) {
- setActiveLabelmapFromImage(imageID);
+ if (isActive.value) {
+ ensureActiveSegmentGroupForImage(imageID);
+ } else {
+ const segmentGroupID = getFirstSegmentGroupID(imageID);
+ if (segmentGroupID) {
+ setActiveSegmentGroup(segmentGroupID);
}
- },
- { immediate: true }
- );
+ }
+ });
return {
// state
@@ -222,8 +238,8 @@ export const usePaintToolStore = defineStore('paint', () => {
deactivateTool,
setMode,
- setActiveLabelmap,
- setActiveLabelmapFromImage,
+ setActiveSegmentGroup,
+ ensureActiveSegmentGroupForImage,
setActiveSegment,
setBrushSize,
setSliceAxis,
diff --git a/src/types/segment.ts b/src/types/segment.ts
index dacfb907..6db17d61 100644
--- a/src/types/segment.ts
+++ b/src/types/segment.ts
@@ -4,4 +4,5 @@ export interface SegmentMask {
value: number;
name: string;
color: RGBAColor;
+ visible: boolean;
}