Skip to content
Merged
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
36 changes: 31 additions & 5 deletions client/dive-common/components/EditorMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ export default defineComponent({
const maskLoadingPercent = computed(() => editorOptions.maskLoadingPercent?.value || 0);
const tooManyMasks = computed(() => editorOptions.tooManyMasks.value);
const loadingFrame = computed(() => editorOptions.loadingFrame.value);
const maskWarningMessage = computed(() => editorOptions.maskWarningMessage.value);
const pauseOnLoading = computed({
get() {
return editorOptions.pauseOnLoading.value;
Expand All @@ -335,6 +336,15 @@ export default defineComponent({
offsetX: 25,
};
}
if (maskWarningMessage.value) {
return {
icon: 'mdi-alert-circle-outline',
color: 'warning',
value: true,
content: undefined,
offsetX: 25,
};
}
if (loadingFrame.value) {
return {
icon: 'mdi-spin mdi-sync',
Expand Down Expand Up @@ -382,6 +392,7 @@ export default defineComponent({
maskIcon,
updateMaskCacheSeconds,
loadingFrame,
maskWarningMessage,
buttonOptions,
menuOptions,
editWhiteColorScale,
Expand Down Expand Up @@ -475,7 +486,7 @@ export default defineComponent({
v-if="button.tooltip
&& (getUISetting('UIVisibility') === true || getUISetting('UIVisibility')[index])"
>
<template #activator="{ on }">
<template #activator="{ on: tooltipOn }">
<v-badge
v-if="button.id === 'Mask'"
overlap
Expand All @@ -494,14 +505,14 @@ export default defineComponent({
offset-y
:close-on-content-click="false"
>
<template #activator="{ on, attrs }">
<template #activator="{ on: menuOn, attrs }">
<v-btn
v-if="getUISetting('Masks')"
v-bind="attrs"
:color="isVisible('Mask') ? 'grey darken-2' : ''"
class="mx-1 mode-button"
small
v-on="on"
v-on="{ ...tooltipOn, ...menuOn }"
@click="button.click"
>
<v-icon>{{ button.icon }}</v-icon>
Expand All @@ -512,6 +523,15 @@ export default defineComponent({
outlined
>
<v-card-text>Segementation Masks</v-card-text>
<v-alert
v-if="maskWarningMessage"
dense
text
type="warning"
class="mb-2"
>
{{ maskWarningMessage }}
</v-alert>
<v-progress-linear
v-if="maskLoadingPercent && maskLoadingPercent < 100"
:value="maskLoadingPercent"
Expand Down Expand Up @@ -557,13 +577,19 @@ export default defineComponent({
:color="button.active ? 'grey darken-2' : ''"
class="mx-1 mode-button"
small
v-on="on"
v-on="tooltipOn"
@click="button.click"
>
<v-icon>{{ button.icon }}</v-icon>
</v-btn>
</template>
<span> {{ button.tooltip }}</span>
<span v-if="button.id === 'Mask' && tooManyMasks">
Too many masks in the current cache window.
</span>
<span v-else-if="button.id === 'Mask' && maskWarningMessage">
{{ maskWarningMessage }}
</span>
<span v-else> {{ button.tooltip }}</span>
</v-tooltip>
<v-btn
v-else-if="getUISetting('UIVisibility') === true || getUISetting('UIVisibility')[index]"
Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dive-dsa",
"version": "1.11.25",
"version": "1.11.26",
"author": {
"name": "Kitware, Inc.",
"email": "Bryon.Lewis@kitware.com"
Expand Down
1 change: 1 addition & 0 deletions client/src/provides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ function dummyState(): State {
opacity: ref(0.75),
triggerAction: ref(null),
loadingFrame: ref(false),
maskWarningMessage: ref(null),
useRLE: ref(false),
},
editorFunctions: {
Expand Down
61 changes: 59 additions & 2 deletions client/src/use/useMasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface UseMaskInterface {
pauseOnLoading: Ref<boolean>;
triggerAction: Ref<null | 'save' | 'delete'>; // Used to communicate with the MaskEditorLayer
loadingFrame: Ref<string | false>;
maskWarningMessage: Ref<string | null>;
useRLE: Ref<boolean>;
};
editorFunctions: {
Expand Down Expand Up @@ -169,6 +170,23 @@ export default function useMasks(
const pauseOnLoading = ref(false);
const maxBrushSize = ref(50);
const loadingFrame: Ref<string | false> = ref(false);
const maskWarningMessage = ref<string | null>(null);

function tracksHaveMasksConfigured() {
return visibleMaskIds.value.length > 0;
}

function setMaskWarning(message: string) {
if (!tracksHaveMasksConfigured()) {
return;
}
maskWarningMessage.value = message;
loadingFrame.value = false;
}

function clearMaskWarning() {
maskWarningMessage.value = null;
}

function setEditorOptions(data: {
toolEnabled?: MaskEditingTools;
Expand Down Expand Up @@ -270,6 +288,11 @@ export default function useMasks(

async function getFolderRLEMasks(folderId: string) {
rleMasks.value = (await getRLEMaskData(folderId)).data;
if (tracksHaveMasksConfigured() && Object.keys(rleMasks.value).length === 0) {
setMaskWarning('Mask metadata is enabled, but rlemasks.json is empty or missing data.');
} else {
clearMaskWarning();
}
}

function initializeMaskData(maskData: { masks: Readonly<MaskItem[]> }) {
Expand Down Expand Up @@ -323,6 +346,7 @@ export default function useMasks(
results.forEach(({ trackId, frameId, mask }) => {
const key = `${frameId}_${trackId}`;
rleCache.set(key, mask);
clearMaskWarning();
if (rleCache.size === masks.value.length) {
const endTime = performance.now();
if (ENABLE_TIMING_LOGS) {
Expand Down Expand Up @@ -463,6 +487,7 @@ export default function useMasks(
const key = `${frameId}_${trackId}`;
rleCache.set(key, mask);
inFlightWorker.delete(key);
clearMaskWarning();
maskLoadingPercent.value = (1 - (inFlightWorker.size / subsetSize)) * 100;
if (rleCache.size === subsetSize) {
const endTime = performance.now();
Expand Down Expand Up @@ -498,11 +523,13 @@ export default function useMasks(
// Now calculate how many seconds we can cache without exceeding MAX_TEXTURE_PRELOAD
let cumulativeMasks = 0;
let maxSeconds = 0;
let exceededTextureLimit = false;
const sortedFrameIds = Object.keys(frameMaskCountMap).map((f) => Number(f)).sort((a, b) => a - b);
for (let i = 0; i < sortedFrameIds.length; i += 1) {
const frameId = sortedFrameIds[i];
cumulativeMasks += frameMaskCountMap[frameId];
if (cumulativeMasks > MAX_TEXTURE_PRELOAD) {
exceededTextureLimit = true;
break;
}
maxSeconds = (frameId - frame.value) / frameRate.value;
Expand All @@ -515,7 +542,8 @@ export default function useMasks(
if (maskCacheSeconds.value > maskMaxCacheSeconds.value) {
maskCacheSeconds.value = maskMaxCacheSeconds.value;
}
if (maskCacheSeconds.value <= 0.1) {
// Only show "too many masks" when we actually hit the texture preload limit.
if (exceededTextureLimit && maskMaxCacheSeconds.value <= 0.1) {
tooManyMasks.value = true;
} else {
tooManyMasks.value = false;
Expand All @@ -526,6 +554,7 @@ export default function useMasks(
const key = frameKey(frameId, trackId);
const cacheFound = cache.get(key);
if (cacheFound) {
clearMaskWarning();
return cacheFound;
}
if (useRLE.value) {
Expand All @@ -534,12 +563,21 @@ export default function useMasks(
loadingFrame.value = key;
return undefined;
}
if (!mask) {
setMaskWarning(`Missing RLE mask for track ${trackId}, frame ${frameId}. Check rlemasks.json mapping.`);
}
} else {
const key = frameKey(frameId, trackId);
if (inFlightRequests.has(key)) {
loadingFrame.value = key;
return undefined;
}
const hasMaskFile = masks.value.some((item) => (
item.metadata?.trackId === trackId && Number(item.metadata?.frameId) === frameId
));
if (!hasMaskFile) {
setMaskWarning(`Missing mask file for track ${trackId}, frame ${frameId}.`);
}
}
return undefined;
}
Expand All @@ -564,6 +602,9 @@ export default function useMasks(
});

watch(visibleMaskIds, () => {
if (!tracksHaveMasksConfigured()) {
clearMaskWarning();
}
if (maskModeEnabled.value && typeof frame.value === 'number') {
preloadWindow(frame.value);
} else if (visibleMaskIds.value.length > 0) {
Expand Down Expand Up @@ -605,9 +646,24 @@ export default function useMasks(
const key = frameKey(frameId, trackId);
const cacheFound = rleCache.get(key);
if (cacheFound) {
clearMaskWarning();
return cacheFound;
}
loadingFrame.value = key;
if (inFlightWorker.has(key)) {
loadingFrame.value = key;
return undefined;
}
const hasRLEMask = Boolean(rleMasks.value[trackId]?.[frameId]);
if (!hasRLEMask) {
setMaskWarning(`Missing RLE mask for track ${trackId}, frame ${frameId}. Check rlemasks.json mapping.`);
return undefined;
}
preloadWindow(frame.value);
if (inFlightWorker.has(key)) {
loadingFrame.value = key;
} else {
setMaskWarning(`Unable to load RLE mask for track ${trackId}, frame ${frameId}.`);
}
return undefined;
};
return {
Expand All @@ -629,6 +685,7 @@ export default function useMasks(
tooManyMasks,
maxBrushSize,
loadingFrame,
maskWarningMessage,
useRLE,
maskLoadingPercent,
pauseOnLoading,
Expand Down
Loading