Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
54ecd91
feat(labelmap): improve labelmap store API
floryst Oct 4, 2023
c717a98
feat(LabelmapList): select active paint labelmap
floryst Oct 4, 2023
438283d
fix(types): export proper vtk types
floryst Oct 4, 2023
6fcca71
fix(ImageListCard): remove unnecessary div
floryst Oct 4, 2023
6f25f78
feat(compareImageSpace): better space comparison
floryst Oct 4, 2023
eff8bde
feat: add buttons to convert to labelmap
floryst Oct 4, 2023
045f8f7
fix(LabelmapList): guard against empty labelmaps
floryst Oct 5, 2023
465ca26
refactor(LabelEditor): make label editor generic
floryst Oct 12, 2023
79cf60a
feat(IsolatedDialog): event-isolated dialog
floryst Oct 13, 2023
8135d43
feat(LabelEditor): add cancel capability
floryst Oct 13, 2023
ddeb75c
fix(shortcuts): modifiers trigger no-mod shortcuts
floryst Oct 13, 2023
3b68ef7
feat: add initial segment editor
floryst Oct 13, 2023
c58e526
refactor(labelmap): component renaming
floryst Oct 23, 2023
674ddd0
fix(ToolLabelEditor): emit strokeWidth event
floryst Oct 23, 2023
4aad7de
feat: unify label and segment editable list UX
floryst Oct 23, 2023
aa2db57
feat(labelmap): copy segments from cur labelmap
floryst Oct 24, 2023
44255f5
feat(labelmap): nicer default names
floryst Oct 24, 2023
8e5de65
fix(labelmap): serialize non-proxied segments
floryst Oct 24, 2023
3be7adb
feat(ModulePanel): show annotations on paint tool
floryst Oct 25, 2023
7751258
feat(paint): disable paint if no active segment
floryst Oct 25, 2023
b43f445
feat(labelmaps): pick unique name
floryst Oct 25, 2023
1cc803a
fix(LabelmapControls): minor margin tweak
floryst Oct 25, 2023
9abddf3
fix(LabelmapControls): remove convert-to-image
floryst Oct 25, 2023
ac34602
fix(dicom): set image name to the series desc
floryst Oct 25, 2023
f3f05ba
fix(LabelmapControls): Edit labelmap -> Name
floryst Oct 25, 2023
2a6bfbe
fix: build issues
floryst Oct 25, 2023
a8987f3
feat(AnnotationsModule): use tabs for annotations
floryst Oct 31, 2023
70220e8
feat(paint): add eraser
floryst Nov 3, 2023
5ccc1cf
Merge remote-tracking branch 'origin/main' into convert-to-labelmap
floryst Nov 3, 2023
0d10237
fix(ImageDataBrowser): remove labelmap convert btn
floryst Nov 6, 2023
d5785ed
feat(paint): add eraser mode
floryst Nov 6, 2023
c685ade
feat(labelmaps): ask for name upon creation
floryst Nov 6, 2023
2f7f8a4
feat(LabelmapControls): radio button list
floryst Nov 6, 2023
4a34db6
feat(LabelmapControls): better styling
floryst Nov 6, 2023
ea697a1
feat(AnnotationsModule): visual separator
floryst Nov 6, 2023
a1d279e
feat(LabelmapControls): enforce name uniqueness
floryst Nov 7, 2023
3c20960
refactor: rename labelmaps to segment groups
floryst Nov 7, 2023
9598381
Merge pull request #494 from Kitware/rename-labelmaps-to-segment-groups
floryst Nov 9, 2023
6c5633e
Merge remote-tracking branch 'origin/main' into convert-to-labelmap
floryst Nov 9, 2023
9cc82e1
fix(datasets-dicom): duplicate cleanupName
floryst Nov 9, 2023
2936961
feat(SegmentGroupControls): improve new group UI
floryst Nov 13, 2023
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
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ module.exports = {
ignores: ['Settings'],
},
],
'prefer-destructuring': 'off',
},

overrides: [
Expand Down
68 changes: 49 additions & 19 deletions src/components/AnnotationsModule.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<script setup lang="ts">
import { AnnotationToolType } from '@/src/store/tools/types';
import { ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { AnnotationToolType, Tools } from '@/src/store/tools/types';
import { useToolStore } from '@/src/store/tools';
import MeasurementsToolList from './MeasurementsToolList.vue';
import LabelmapList from './LabelmapList.vue';
import SegmentGroupControls from './SegmentGroupControls.vue';
import ToolControls from './ToolControls.vue';
import MeasurementRulerDetails from './MeasurementRulerDetails.vue';

const tools = [
const Tabs = {
Measurements: 'measurements',
SegmentGroups: 'segmentGroups',
};

const MeasurementTools = [
{
type: AnnotationToolType.Ruler,
icon: 'mdi-ruler',
Expand All @@ -20,31 +28,53 @@ const tools = [
icon: 'mdi-pentagon-outline',
},
];

const MeasurementToolTypes = new Set<string>(
MeasurementTools.map(({ type }) => type)
);

const tab = ref(Tabs.Measurements);
const { currentTool } = storeToRefs(useToolStore());

function autoFocusTab() {
if (currentTool.value === Tools.Paint) {
tab.value = Tabs.SegmentGroups;
} else if (MeasurementToolTypes.has(currentTool.value)) {
tab.value = Tabs.Measurements;
}
}

watch(
currentTool,
() => {
autoFocusTab();
},
{ immediate: true }
);
</script>

<template>
<div class="overflow-y-auto mx-2 fill-height">
<tool-controls />
<div class="header">Measurements</div>
<div class="content">
<measurements-tool-list :tools="tools" />
</div>
<div class="text-caption text-center empty-state">No measurements</div>
<div class="header">Labelmaps</div>
<div class="content">
<labelmap-list />
</div>
<div class="text-caption text-center empty-state">No labelmaps</div>
<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="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="segmentGroups">
<segment-group-controls />
</v-window-item>
</v-window>
</div>
</template>

<style scoped>
.empty-state {
display: none;
}

.content:empty + .empty-state {
display: block;
.tab-header {
font-size: 0.8rem;
}
</style>

Expand Down
12 changes: 3 additions & 9 deletions src/components/CloseableDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* globals defineOptions */
import { computed, PropType } from 'vue';
import { useDisplay } from 'vuetify';
import IsolatedDialog from '@/src/components/IsolatedDialog.vue';

defineOptions({
inheritAttrs: false,
Expand Down Expand Up @@ -44,23 +45,16 @@ const width = computed(() => {
const maxWidth = computed(() => {
return display.mobile.value ? '100%' : props.maxWidth;
});

const stopAllButEscape = (e: KeyboardEvent) => {
if (e.key !== 'Escape') {
e.stopPropagation();
}
};
</script>

<template>
<v-dialog
<isolated-dialog
v-bind="$attrs"
:content-class="['closeable-dialog', $attrs['content-class']]"
:width="width"
:max-width="maxWidth"
:model-value="props.modelValue"
@update:model-value="$emit('update:model-value', $event)"
@keydown="stopAllButEscape"
>
<v-btn
variant="flat"
Expand All @@ -73,7 +67,7 @@ const stopAllButEscape = (e: KeyboardEvent) => {
@click="close"
/>
<slot :close="close" />
</v-dialog>
</isolated-dialog>
</template>

<style scoped>
Expand Down
74 changes: 74 additions & 0 deletions src/components/EditableChipList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script
setup
lang="ts"
,
generic="T, KeyProp extends keyof T, TitleProp extends keyof T"
>
/* global T, KeyProp, TitleProp */

import { computed } from 'vue';
import { Maybe } from '@/src/types';

defineEmits(['select', 'edit', 'create', 'update:model-value']);

const props = withDefaults(
defineProps<{
items: Array<T>;
itemKey: T[KeyProp] extends string | number | symbol ? KeyProp : never;
itemTitle: T[TitleProp] extends string ? TitleProp : never;
createLabelText?: string;
modelValue: Maybe<T[KeyProp]>;
}>(),
{
createLabelText: 'Create Label',
}
);

const itemsToRender = computed(() =>
props.items.map((item) => ({
key: item[props.itemKey] as string | number | symbol,
title: item[props.itemTitle],
}))
);
</script>

<template>
<v-item-group
:model-value="modelValue"
@update:model-value="$emit('update:model-value', $event)"
selected-class="selected"
mandatory
>
<v-row dense>
<v-col cols="6" v-for="({ key, title }, idx) in itemsToRender" :key="key">
<v-item v-slot="{ selectedClass, toggle }" :value="key">
<v-chip
variant="tonal"
:class="['w-100 d-flex', selectedClass]"
@click="toggle"
>
<slot name="item-prepend" :key="key" :item="items[idx]"></slot>
<span class="overflow-hidden">{{ title }}</span>
<v-spacer />
<slot name="item-append" :key="key" :item="items[idx]"></slot>
</v-chip>
</v-item>
</v-col>

<!-- Add Label button -->
<v-col cols="6">
<v-chip variant="outlined" class="w-100" @click="$emit('create')">
<v-icon class="mr-2">mdi-plus</v-icon>
{{ createLabelText }}
</v-chip>
</v-col>
</v-row>
</v-item-group>
</template>

<style scoped>
.selected {
background-color: rgb(var(--v-theme-selection-bg-color));
border-color: rgb(var(--v-theme-selection-border-color));
}
</style>
91 changes: 62 additions & 29 deletions src/components/ImageDataBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ export default defineComponent({
type: 'image',
dataID: id,
} as DataSelection;
const layerAdded = layerImageIDs.includes(id);
const isLayer = layerImageIDs.includes(id);
const layerLoaded = loadedLayerImageIDs.includes(id);
const loading = layerAdded && !layerLoaded;
const layerLoading = isLayer && !layerLoaded;
const layerable = id !== selectedImageID && primarySelection.value;
return {
id,
Expand All @@ -72,12 +72,11 @@ export default defineComponent({
dimensions: metadata[id].dimensions,
spacing: [...metadata[id].spacing].map((s) => s.toFixed(2)),
layerable,
loading,
layerIcon: layerAdded ? 'mdi-layers-minus' : 'mdi-layers-plus',
layerTooltip: layerAdded ? 'Remove Layer' : 'Add Layer',
layerLoading,
isLayer,
layerHandler: () => {
if (!loading && layerable) {
if (layerAdded)
if (!layerLoading && layerable) {
if (isLayer)
layersStore.deleteLayer(primarySelection.value, selectionKey);
else layersStore.addLayer(primarySelection.value, selectionKey);
}
Expand Down Expand Up @@ -133,12 +132,17 @@ export default defineComponent({
selected.value = [];
}

function removeData(id: string) {
imageStore.deleteData(id);
}

return {
selected,
selectedAll,
selectedSome,
toggleSelectAll,
removeSelection,
removeData,
images,
thumbnails,
primarySelection,
Expand Down Expand Up @@ -199,7 +203,7 @@ export default defineComponent({
>
<image-list-card
v-model="selected"
class="mt-1"
class="mt-1 position-relative"
selectable
:inputValue="image.id"
:active="active"
Expand All @@ -209,30 +213,59 @@ export default defineComponent({
:id="image.id"
@click="select"
>
<div class="text-body-2 font-weight-bold text-no-wrap text-truncate">
{{ image.name }}
</div>
<div class="text-caption">
Dims: ({{ image.dimensions.join(', ') }})
</div>
<div class="text-caption">
Spacing: ({{ image.spacing.join(', ') }})
<div class="d-flex flex-row justify-space-between">
<div class="allow-trunc-text-flex-child">
<div
class="text-body-2 font-weight-bold text-no-wrap text-truncate"
>
{{ image.name }}
</div>
<div class="text-caption">
Dims: ({{ image.dimensions.join(', ') }})
</div>
<div class="text-caption">
Spacing: ({{ image.spacing.join(', ') }})
</div>
</div>
<v-btn icon variant="plain" size="x-small" class="dataset-menu">
<v-menu activator="parent">
<v-list>
<v-list-item
v-if="image.layerable"
@click.stop="image.layerHandler()"
>
<template v-if="image.layerLoading">
<div style="margin: 0 auto">
<v-progress-circular indeterminate size="small" />
</div>
</template>
<template v-else>
<span v-if="image.isLayer">Remove as layer</span>
<span v-else>Add as layer</span>
</template>
</v-list-item>
<v-list-item @click="removeData(image.id)">
Delete
</v-list-item>
</v-list>
</v-menu>
<v-icon size="medium">mdi-dots-vertical</v-icon>
</v-btn>
</div>
<v-btn
icon
variant="text"
:disabled="!image.layerable"
:loading="image.loading"
@click.stop="image.layerHandler()"
class="mt-1"
>
<v-icon :icon="image.layerIcon" />
<v-tooltip location="top" activator="parent">
{{ image.layerTooltip }}
</v-tooltip>
</v-btn>
</image-list-card>
</groupable-item>
</item-group>
</div>
</template>

<style scoped>
.allow-trunc-text-flex-child {
min-width: 0;
}

.dataset-menu {
position: relative;
top: -8px;
right: -4px;
}
</style>
6 changes: 2 additions & 4 deletions src/components/ImageListCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@
<slot name="image-overlay" />
</persistent-overlay>
</v-col>
<v-col :cols="selectable ? 7 : 8">
<div class="ml-2">
<slot></slot>
</div>
<v-col :cols="selectable ? 7 : 8" class="ml-2">
<slot></slot>
</v-col>
</v-row>
</v-container>
Expand Down
10 changes: 10 additions & 0 deletions src/components/IsolatedDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup lang="ts">
const stopAllButEscape = (e: KeyboardEvent) => {
if (e.key !== 'Escape') {
e.stopPropagation();
}
};
</script>
<template>
<v-dialog @keydown="stopAllButEscape"><slot></slot></v-dialog>
</template>
Loading