Skip to content

Commit

Permalink
Merge pull request #494 from Kitware/rename-labelmaps-to-segment-groups
Browse files Browse the repository at this point in the history
refactor: rename labelmaps to segment groups
  • Loading branch information
floryst authored Nov 9, 2023
2 parents a1d279e + 3c20960 commit 9598381
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 198 deletions.
14 changes: 7 additions & 7 deletions src/components/AnnotationsModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { storeToRefs } from 'pinia';
import { AnnotationToolType, Tools } from '@/src/store/tools/types';
import { useToolStore } from '@/src/store/tools';
import MeasurementsToolList from './MeasurementsToolList.vue';
import LabelmapControls from './LabelmapControls.vue';
import SegmentGroupControls from './SegmentGroupControls.vue';
import ToolControls from './ToolControls.vue';
import MeasurementRulerDetails from './MeasurementRulerDetails.vue';
const Tabs = {
Measurements: 'measurements',
Labelmaps: 'labelmaps',
SegmentGroups: 'segmentGroups',
};
const MeasurementTools = [
Expand All @@ -33,12 +33,12 @@ const MeasurementToolTypes = new Set<string>(
MeasurementTools.map(({ type }) => type)
);
const tab = ref('measurements');
const tab = ref(Tabs.Measurements);
const { currentTool } = storeToRefs(useToolStore());
function autoFocusTab() {
if (currentTool.value === Tools.Paint) {
tab.value = Tabs.Labelmaps;
tab.value = Tabs.SegmentGroups;
} else if (MeasurementToolTypes.has(currentTool.value)) {
tab.value = Tabs.Measurements;
}
Expand All @@ -59,14 +59,14 @@ watch(
<v-divider thickness="4" />
<v-tabs v-model="tab" align-tabs="center" density="compact" class="my-1">
<v-tab value="measurements" class="tab-header">Measurements</v-tab>
<v-tab value="labelmaps" class="tab-header">Labelmaps</v-tab>
<v-tab value="segmentGroups" class="tab-header">Segment Groups</v-tab>
</v-tabs>
<v-window v-model="tab">
<v-window-item value="measurements">
<measurements-tool-list :tools="MeasurementTools" />
</v-window-item>
<v-window-item value="labelmaps">
<labelmap-controls />
<v-window-item value="segmentGroups">
<segment-group-controls />
</v-window-item>
</v-window>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,59 +1,60 @@
<script setup lang="ts">
import SegmentList from '@/src/components/SegmentList.vue';
import { useCurrentImage } from '@/src/composables/useCurrentImage';
import { useLabelmapStore } from '@/src/store/datasets-labelmaps';
import { useSegmentGroupStore } from '@/src/store/segmentGroups';
import { usePaintToolStore } from '@/src/store/tools/paint';
import { Maybe } from '@/src/types';
import { reactive, ref, computed, watch, toRaw } from 'vue';
const UNNAMED_LABELMAP_NAME = 'Unnamed Labelmap';
const UNNAMED_GROUP_NAME = 'Unnamed Segment Group';
const labelmapStore = useLabelmapStore();
const segmentGroupStore = useSegmentGroupStore();
const { currentImageID } = useCurrentImage();
const currentLabelmaps = computed(() => {
const currentSegmentGroups = computed(() => {
if (!currentImageID.value) return [];
if (!(currentImageID.value in labelmapStore.orderByParent)) return [];
return labelmapStore.orderByParent[currentImageID.value].map((id) => {
const { orderByParent, metadataByID } = segmentGroupStore;
if (!(currentImageID.value in orderByParent)) return [];
return orderByParent[currentImageID.value].map((id) => {
return {
id,
name: labelmapStore.labelmapMetadata[id].name,
name: metadataByID[id].name,
};
});
});
const paintStore = usePaintToolStore();
const selectedLabelmapID = computed({
get: () => paintStore.activeLabelmapID,
const currentSegmentGroupID = computed({
get: () => paintStore.activeSegmentGroupID,
set: (id) => paintStore.setActiveLabelmap(id),
});
// clear selection if we delete the labelmaps
watch(currentLabelmaps, () => {
const selection = selectedLabelmapID.value;
if (selection && !(selection in labelmapStore.dataIndex)) {
selectedLabelmapID.value = null;
// clear selection if we delete the active segment group
watch(currentSegmentGroups, () => {
const selection = currentSegmentGroupID.value;
if (selection && !(selection in segmentGroupStore.dataIndex)) {
currentSegmentGroupID.value = null;
}
});
function deleteLabelmap(id: string) {
labelmapStore.removeLabelmap(id);
function deleteGroup(id: string) {
segmentGroupStore.removeGroup(id);
}
// --- editing state --- //
const editingLabelmapID = ref<Maybe<string>>(null);
const editingGroupID = ref<Maybe<string>>(null);
const editState = reactive({ name: '' });
const editDialog = ref(false);
const editingMetadata = computed(() => {
if (!editingLabelmapID.value) return null;
return labelmapStore.labelmapMetadata[editingLabelmapID.value];
if (!editingGroupID.value) return null;
return segmentGroupStore.metadataByID[editingGroupID.value];
});
const existingNames = computed(() => {
return new Set(
Object.values(labelmapStore.labelmapMetadata).map((meta) => meta.name)
Object.values(segmentGroupStore.metadataByID).map((meta) => meta.name)
);
});
Expand All @@ -71,7 +72,7 @@ function uniqueNameRule(name: string) {
function startEditing(id: string) {
editDialog.value = true;
editingLabelmapID.value = id;
editingGroupID.value = id;
if (editingMetadata.value) {
editState.name = editingMetadata.value.name;
}
Expand All @@ -81,30 +82,31 @@ function stopEditing(commit: boolean) {
if (editingNameConflict.value) return;
editDialog.value = false;
if (editingLabelmapID.value && commit)
labelmapStore.updateMetadata(editingLabelmapID.value, {
name: editState.name || UNNAMED_LABELMAP_NAME,
if (editingGroupID.value && commit)
segmentGroupStore.updateMetadata(editingGroupID.value, {
name: editState.name || UNNAMED_GROUP_NAME,
});
editingLabelmapID.value = null;
editingGroupID.value = null;
}
// --- //
function createLabelmap() {
function createSegmentGroup() {
if (!currentImageID.value)
throw new Error('Cannot create a labelmap without a base image');
const id = labelmapStore.newLabelmapFromImage(currentImageID.value);
const id = segmentGroupStore.newLabelmapFromImage(currentImageID.value);
if (!id) throw new Error('Could not create a new labelmap');
// copy segments from current labelmap
if (selectedLabelmapID.value) {
const metadata = labelmapStore.labelmapMetadata[selectedLabelmapID.value];
if (currentSegmentGroupID.value) {
const metadata =
segmentGroupStore.metadataByID[currentSegmentGroupID.value];
const copied = structuredClone(toRaw(metadata.segments));
labelmapStore.updateMetadata(id, { segments: copied });
segmentGroupStore.updateMetadata(id, { segments: copied });
}
selectedLabelmapID.value = id;
currentSegmentGroupID.value = id;
startEditing(id);
}
Expand All @@ -116,41 +118,38 @@ function createLabelmap() {
variant="tonal"
color="secondary"
class="mb-4"
@click.stop="createLabelmap"
@click.stop="createSegmentGroup"
>
<v-icon class="mr-1">mdi-plus</v-icon> New Labelmap
<v-icon class="mr-1">mdi-plus</v-icon> New Segment Group
</v-btn>
<div class="text-grey text-subtitle-2">Segment Groups</div>
<v-divider />
<v-radio-group
v-model="selectedLabelmapID"
v-model="currentSegmentGroupID"
hide-details
density="comfortable"
class="my-1 segment-group-list"
>
<v-radio
v-for="labelmap in currentLabelmaps"
:key="labelmap.id"
:value="labelmap.id"
v-for="group in currentSegmentGroups"
:key="group.id"
:value="group.id"
>
<template #label>
<div
class="d-flex flex-row align-center w-100"
:title="labelmap.name"
>
<span class="labelmap-name">{{ labelmap.name }}</span>
<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="mdi-pencil"
size="x-small"
variant="flat"
@click.stop="startEditing(labelmap.id)"
@click.stop="startEditing(group.id)"
></v-btn>
<v-btn
icon="mdi-delete"
size="x-small"
variant="flat"
@click.stop="deleteLabelmap(labelmap.id)"
@click.stop="deleteGroup(group.id)"
></v-btn>
</div>
</template>
Expand All @@ -159,14 +158,17 @@ function createLabelmap() {
<v-divider />
</div>
<div v-else class="text-center text-caption">No selected image</div>
<segment-list v-if="selectedLabelmapID" :labelmap-id="selectedLabelmapID" />
<segment-list
v-if="currentSegmentGroupID"
:group-id="currentSegmentGroupID"
/>

<v-dialog v-model="editDialog" max-width="400px">
<v-card>
<v-card-text>
<v-text-field
v-model="editState.name"
:placeholder="UNNAMED_LABELMAP_NAME"
:placeholder="UNNAMED_GROUP_NAME"
:rules="[uniqueNameRule]"
@keydown.stop.enter="stopEditing(true)"
/>
Expand All @@ -188,11 +190,7 @@ function createLabelmap() {
overflow-y: auto;
}
.labelmap-select > .v-input__append {
margin-left: 8px;
}
.labelmap-name {
.group-name {
word-wrap: none;
white-space: nowrap;
overflow: hidden;
Expand Down
24 changes: 12 additions & 12 deletions src/components/SegmentList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,33 @@ import EditableChipList from '@/src/components/EditableChipList.vue';
import SegmentEditor from '@/src/components/SegmentEditor.vue';
import IsolatedDialog from '@/src/components/IsolatedDialog.vue';
import {
useLabelmapStore,
useSegmentGroupStore,
makeDefaultSegmentName,
} from '@/src/store/datasets-labelmaps';
} from '@/src/store/segmentGroups';
import { Maybe } from '@/src/types';
import { hexaToRGBA, rgbaToHexa } from '@/src/utils/color';
import { reactive, ref, toRefs, computed, watch } from 'vue';
import { LabelMapSegment } from '@/src/types/labelmap';
import { SegmentMask } from '@/src/types/segment';
import { usePaintToolStore } from '@/src/store/tools/paint';
const props = defineProps({
labelmapId: {
groupId: {
required: true,
type: String,
},
});
const { labelmapId } = toRefs(props);
const { groupId } = toRefs(props);
const labelmapStore = useLabelmapStore();
const segmentGroupStore = useSegmentGroupStore();
const paintStore = usePaintToolStore();
const segments = computed<LabelMapSegment[]>(() => {
return labelmapStore.segmentsByLabelmapID[labelmapId.value] ?? [];
const segments = computed<SegmentMask[]>(() => {
return segmentGroupStore.segmentByGroupID[groupId.value] ?? [];
});
function addNewSegment() {
labelmapStore.addSegment(labelmapId.value);
segmentGroupStore.addSegment(groupId.value);
}
// --- selection --- //
Expand Down Expand Up @@ -68,7 +68,7 @@ const editDialog = ref(false);
const editingSegment = computed(() => {
if (editingSegmentValue.value == null) return null;
return labelmapStore.getSegment(labelmapId.value, editingSegmentValue.value);
return segmentGroupStore.getSegment(groupId.value, editingSegmentValue.value);
});
function startEditing(value: number) {
Expand All @@ -81,7 +81,7 @@ function startEditing(value: number) {
function stopEditing(commit: boolean) {
if (editingSegmentValue.value && commit)
labelmapStore.updateSegment(labelmapId.value, editingSegmentValue.value, {
segmentGroupStore.updateSegment(groupId.value, editingSegmentValue.value, {
name: editState.name ?? makeDefaultSegmentName(editingSegmentValue.value),
color: hexaToRGBA(editState.color),
});
Expand All @@ -90,7 +90,7 @@ function stopEditing(commit: boolean) {
}
function deleteSegment(value: number) {
labelmapStore.deleteSegment(labelmapId.value, value);
segmentGroupStore.deleteSegment(groupId.value, value);
}
function deleteEditingSegment() {
Expand Down
10 changes: 5 additions & 5 deletions src/components/VtkTwoView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ import PolygonTool from './tools/polygon/PolygonTool.vue';
import PaintTool from './tools/paint/PaintTool.vue';
import { useSceneBuilder } from '../composables/useSceneBuilder';
import { useDICOMStore } from '../store/datasets-dicom';
import { useLabelmapStore } from '../store/datasets-labelmaps';
import { useSegmentGroupStore } from '../store/segmentGroups';
import vtkLabelMapSliceRepProxy from '../vtk/LabelMapSliceRepProxy';
import { usePaintToolStore } from '../store/tools/paint';
import useWindowingStore from '../store/view-configs/windowing';
Expand Down Expand Up @@ -572,10 +572,10 @@ export default defineComponent({
// --- scene setup --- //
const labelmapStore = useLabelmapStore();
const segmentGroupStore = useSegmentGroupStore();
const labelmapIDs = computed(() =>
curImageID.value ? labelmapStore.orderByParent[curImageID.value] : []
const segmentGroupIDs = computed(() =>
curImageID.value ? segmentGroupStore.orderByParent[curImageID.value] : []
);
const layerIDs = computed(() => currentLayers.value.map(({ id }) => id));
Expand All @@ -586,7 +586,7 @@ export default defineComponent({
vtkIJKSliceRepresentationProxy
>(viewID, {
baseImage: curImageID,
labelmaps: labelmapIDs,
labelmaps: segmentGroupIDs,
layers: layerIDs,
});
Expand Down
4 changes: 2 additions & 2 deletions src/composables/useSceneBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export function useSceneBuilder<
});

const labelmapReps = computed(() => {
const labelmapIDs = sceneIDs.labelmaps?.value ?? [];
return labelmapIDs
const ids = sceneIDs.labelmaps?.value ?? [];
return ids
.map((id) =>
viewStore.getDataRepresentationForView<LabelMapType>(id, viewID.value)
)
Expand Down
4 changes: 2 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MRIPROSTATExThumbnail from '@/src/assets/samples/MRI-PROSTATEx.jpg';
import MRAHeadThumbnail from '@/src/assets/samples/MRA-Head_and_Neck.jpg';
import CTAHeadThumbnail from '@/src/assets/samples/CTA-Head_and_Neck.jpg';
import USFetusThumbnail from '@/src/assets/samples/3DUS-Fetus.jpg';
import { LabelMapSegment } from '@/src/types/labelmap';
import { SegmentMask } from '@/src/types/segment';
import { Layout, LayoutDirection } from './types/layout';
import { ViewSpec } from './types/views';
import { SampleDataset } from './types';
Expand Down Expand Up @@ -158,7 +158,7 @@ export const Layouts: Record<string, Layout> = [
return { ...layouts, [layout.name]: layout };
}, {});

export const DEFAULT_LABELMAP_SEGMENTS: LabelMapSegment[] = [
export const DEFAULT_SEGMENT_MASKS: SegmentMask[] = [
{
value: 1,
name: 'Tissue',
Expand Down
Loading

0 comments on commit 9598381

Please sign in to comment.