Skip to content

Make user bounding box updates more efficient #8492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7bcb4c7
add more supported update actions for user bounding box updates
MichaelBuessemeyer Apr 1, 2025
09de156
[frontend] add new updateActions to add, remove and update individual…
knollengewaechs Apr 3, 2025
7a187fd
WIP: adjust saga code to use new update actions and fixing typing in …
knollengewaechs Apr 3, 2025
cdf546f
[frontend] remove activeUser, timestamp and info from updateActions
knollengewaechs Apr 4, 2025
bfb8746
[frontend] make saga code more DRY and only update changed props
knollengewaechs Apr 4, 2025
78d8d18
[frontend] correctly call helper function
knollengewaechs Apr 4, 2025
6f20124
Merge branch 'master' of github.com:scalableminds/webknossos into mor…
MichaelBuessemeyer Apr 7, 2025
01323e2
Merge branch 'master' of github.com:scalableminds/webknossos into mor…
MichaelBuessemeyer Apr 8, 2025
c680cf2
have single update action for all user bounding box properties
MichaelBuessemeyer Apr 8, 2025
55fdf45
Don't make frontend return a single elemnt list of a user bbox for ne…
MichaelBuessemeyer Apr 8, 2025
d8dcfdd
[frontend] dont send empty update objects
knollengewaechs Apr 9, 2025
b8d0a71
[frontend] WIP: send right bbox object to server
knollengewaechs Apr 9, 2025
21e13e2
[frontend] clean up code
knollengewaechs Apr 9, 2025
374fe71
[frontend] remove unneccesary update actions sent to backend
knollengewaechs Apr 10, 2025
c6a9b0f
[frontend] remove old actions
knollengewaechs Apr 10, 2025
0ae286c
[frontend] use updateUserBoundingBoxVisibilityActions
knollengewaechs Apr 23, 2025
675ae35
[frontend] adjust new update actions name fields
knollengewaechs Apr 23, 2025
4d33b5c
[frontend] rename actions to ...In[Volume|Skeleton]TracingAction
knollengewaechs Apr 25, 2025
b28de89
Merge branch 'master' of github.com:scalableminds/webknossos into mor…
MichaelBuessemeyer May 7, 2025
3ed226f
fix merge
MichaelBuessemeyer May 7, 2025
ae851c3
re-add old UpdateUserBoundingBoxVisibility action & fix naming of act…
MichaelBuessemeyer May 7, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,39 @@ function removeSubsequentUpdateNodeActions(updateActionsBatches: Array<SaveQueue
return _.without(updateActionsBatches, ...obsoleteUpdateActions);
}

function removeSubsequentUpdateBBoxActions(updateActionsBatches: Array<SaveQueueEntry>) {
const obsoleteUpdateActions = [];

// Actions are obsolete, if they are for the same bounding box and for the same prop.
// The given action is always compared to the next next one, as usually an
// updateUserBoundingBoxInSkeletonTracingAction and updateUserBoundingBoxInVolumeTracingAction
// is sent at the same time.
for (let i = 0; i < updateActionsBatches.length - 2; i++) {
const actions1 = updateActionsBatches[i].actions;
const actions2 = updateActionsBatches[i + 2].actions;

if (
actions1.length === 1 &&
actions2.length === 1 &&
(actions1[0].name === "updateUserBoundingBoxInSkeletonTracing" ||
actions1[0].name === "updateUserBoundingBoxInVolumeTracing") &&
actions1[0].name === actions2[0].name &&
actions1[0].value.boundingBoxId === actions2[0].value.boundingBoxId &&
_.isEqual(actions1[0].value.updatedPropKeys, actions2[0].value.updatedPropKeys)
) {
obsoleteUpdateActions.push(updateActionsBatches[i]);
console.log(
actions1[0].value.boundingBoxId,
actions1[0].value.updatedProps,
actions2[0].value.boundingBoxId,
actions2[0].value.updatedProps,
); // TODO_c remove
}
}

return _.without(updateActionsBatches, ...obsoleteUpdateActions);
}

function removeSubsequentUpdateSegmentActions(updateActionsBatches: Array<SaveQueueEntry>) {
const obsoleteUpdateActions = [];

Expand Down Expand Up @@ -100,10 +133,12 @@ export default function compactSaveQueue(
(updateActionsBatch) => updateActionsBatch.actions.length > 0,
);

return removeSubsequentUpdateSegmentActions(
removeSubsequentUpdateTreeActions(
removeSubsequentUpdateNodeActions(
removeAllButLastUpdateTdCameraAction(removeAllButLastUpdateTracingAction(result)),
return removeSubsequentUpdateBBoxActions(
removeSubsequentUpdateSegmentActions(
removeSubsequentUpdateTreeActions(
removeSubsequentUpdateNodeActions(
removeAllButLastUpdateTdCameraAction(removeAllButLastUpdateTracingAction(result)),
),
),
),
);
Expand Down
11 changes: 5 additions & 6 deletions frontend/javascripts/oxalis/model/reducers/reducer_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,12 @@ export function convertUserBoundingBoxesFromServerToFrontend(
};
});
}

export function convertUserBoundingBoxesFromFrontendToServer(
boundingBoxes: Array<UserBoundingBox>,
): Array<UserBoundingBoxToServer> {
return boundingBoxes.map((bb) => {
const { boundingBox, ...rest } = bb;
return { ...rest, boundingBox: Utils.computeBoundingBoxObjectFromBoundingBox(boundingBox) };
});
boundingBox: UserBoundingBox,
): UserBoundingBoxToServer {
const { boundingBox: bb, ...rest } = boundingBox;
return { ...rest, boundingBox: Utils.computeBoundingBoxObjectFromBoundingBox(bb) };
}
export function convertFrontendBoundingBoxToServer(
boundingBox: BoundingBoxType,
Expand Down
89 changes: 81 additions & 8 deletions frontend/javascripts/oxalis/model/sagas/skeletontracing_saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,26 @@ import type { Saga } from "oxalis/model/sagas/effect-generators";
import { select } from "oxalis/model/sagas/effect-generators";
import type { UpdateActionWithoutIsolationRequirement } from "oxalis/model/sagas/update_actions";
import {
addUserBoundingBoxInSkeletonTracingAction,
addUserBoundingBoxInVolumeTracingAction,
createEdge,
createNode,
createTree,
deleteEdge,
deleteNode,
deleteTree,
deleteUserBoundingBoxInSkeletonTracingAction,
deleteUserBoundingBoxInVolumeTracingAction,
updateNode,
updateSkeletonTracing,
updateTree,
updateTreeEdgesVisibility,
updateTreeGroups,
updateTreeVisibility,
updateUserBoundingBoxesInSkeletonTracing,
updateUserBoundingBoxInSkeletonTracingAction,
updateUserBoundingBoxInVolumeTracingAction,
updateUserBoundingBoxVisibilityInSkeletonTracingAction,
updateUserBoundingBoxVisibilityInVolumeTracingAction,
} from "oxalis/model/sagas/update_actions";
import { api } from "oxalis/singletons";
import type {
Expand All @@ -70,6 +77,7 @@ import type {
SkeletonTracing,
Tree,
TreeMap,
UserBoundingBox,
WebknossosState,
} from "oxalis/store";
import Store from "oxalis/store";
Expand All @@ -84,7 +92,7 @@ import {
takeEvery,
throttle,
} from "typed-redux-saga";
import type { ServerSkeletonTracing } from "types/api_types";
import { AnnotationLayerEnum, type ServerSkeletonTracing } from "types/api_types";
import { ensureWkReady } from "./ready_sagas";
import { takeWithBatchActionSupport } from "./saga_helpers";

Expand Down Expand Up @@ -607,6 +615,71 @@ export const cachedDiffTrees = memoizeOne((tracingId: string, prevTrees: TreeMap
Array.from(diffTrees(tracingId, prevTrees, trees)),
);

export function* diffBoundingBoxes(
prevBoundingBoxes: UserBoundingBox[],
currentBoundingBoxes: UserBoundingBox[],
tracingId: string,
tracingType: AnnotationLayerEnum,
) {
const {
onlyA: deletedBBoxIds,
onlyB: addedBBoxIds,
both: maybeChangedBBoxIds,
} = Utils.diffArrays(
_.map(prevBoundingBoxes, (bbox) => bbox.id),
_.map(currentBoundingBoxes, (bbox) => bbox.id),
);
const addBBoxAction =
tracingType === AnnotationLayerEnum.Skeleton
? addUserBoundingBoxInSkeletonTracingAction
: addUserBoundingBoxInVolumeTracingAction;
const deleteBBoxAction =
tracingType === AnnotationLayerEnum.Skeleton
? deleteUserBoundingBoxInSkeletonTracingAction
: deleteUserBoundingBoxInVolumeTracingAction;
const updateBBoxAction =
tracingType === AnnotationLayerEnum.Skeleton
? updateUserBoundingBoxInSkeletonTracingAction
: updateUserBoundingBoxInVolumeTracingAction;
const updateBBoxVisibilityAction =
tracingType === AnnotationLayerEnum.Skeleton
? updateUserBoundingBoxVisibilityInSkeletonTracingAction
: updateUserBoundingBoxVisibilityInVolumeTracingAction;
const getErrorMessage = (id: number) =>
`User bounding box with id ${id} not found in ${tracingType} tracing.`;
for (const id of deletedBBoxIds) {
yield deleteBBoxAction(id, tracingId);
}
for (const id of addedBBoxIds) {
const bbox = currentBoundingBoxes.find((bbox) => bbox.id === id);
if (bbox) {
yield addBBoxAction(bbox, tracingId);
} else {
Toast.error(getErrorMessage(id));
}
}
for (const id of maybeChangedBBoxIds) {
const currentBbox = currentBoundingBoxes.find((bbox) => bbox.id === id);
const prevBbox = prevBoundingBoxes.find((bbox) => bbox.id === id);
if (currentBbox == null || prevBbox == null) {
Toast.error(getErrorMessage(id));
continue;
}
const diffBBox = Utils.diffObjects(currentBbox, prevBbox);
if (_.isEmpty(diffBBox)) continue;
const changedKeys = Object.keys(diffBBox);
if (changedKeys.includes("isVisible")) {
yield updateBBoxVisibilityAction(currentBbox.id, currentBbox.isVisible, tracingId);
continue;
}
if (changedKeys.includes("boundingBox")) {
diffBBox.boundingBox.min = currentBbox.boundingBox.min;
diffBBox.boundingBox.max = currentBbox.boundingBox.max;
}
yield updateBBoxAction(currentBbox.id, diffBBox, tracingId);
}
}

export function* diffSkeletonTracing(
prevSkeletonTracing: SkeletonTracing,
skeletonTracing: SkeletonTracing,
Expand Down Expand Up @@ -637,12 +710,12 @@ export function* diffSkeletonTracing(
);
}

if (!_.isEqual(prevSkeletonTracing.userBoundingBoxes, skeletonTracing.userBoundingBoxes)) {
yield updateUserBoundingBoxesInSkeletonTracing(
skeletonTracing.userBoundingBoxes,
skeletonTracing.tracingId,
);
}
yield* diffBoundingBoxes(
prevSkeletonTracing.userBoundingBoxes,
skeletonTracing.userBoundingBoxes,
skeletonTracing.tracingId,
AnnotationLayerEnum.Skeleton,
);
}
export default [
watchSkeletonTracingAsync,
Expand Down
Loading
Loading