Skip to content

Commit af5f23c

Browse files
committed
write and fix volume specs for UA application
1 parent 3cb3299 commit af5f23c

File tree

5 files changed

+220
-5
lines changed

5 files changed

+220
-5
lines changed

frontend/javascripts/test/fixtures/volumetracing_object.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const stateWithoutDatasetInitialization = update(defaultState, {
6060
volumes: {
6161
$set: [volumeTracing],
6262
},
63+
readOnly: { $set: null },
6364
},
6465
dataset: {
6566
dataSource: {
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import update from "immutability-helper";
2+
import _ from "lodash";
3+
import { sampleTracingLayer } from "test/fixtures/dataset_server_object";
4+
import { initialState as defaultVolumeState } from "test/fixtures/volumetracing_object";
5+
import { chainReduce } from "test/helpers/chainReducer";
6+
import type { Action } from "viewer/model/actions/actions";
7+
import {
8+
addUserBoundingBoxAction,
9+
changeUserBoundingBoxAction,
10+
deleteUserBoundingBoxAction,
11+
} from "viewer/model/actions/annotation_actions";
12+
import * as VolumeTracingActions from "viewer/model/actions/volumetracing_actions";
13+
import { setActiveUserBoundingBoxId } from "viewer/model/actions/ui_actions";
14+
import compactUpdateActions from "viewer/model/helpers/compaction/compact_update_actions";
15+
import { diffVolumeTracing } from "viewer/model/sagas/volumetracing_saga";
16+
import type {
17+
ApplicableVolumeUpdateAction,
18+
UpdateActionWithoutIsolationRequirement,
19+
} from "viewer/model/sagas/update_actions";
20+
import { combinedReducer, type WebknossosState } from "viewer/store";
21+
import { makeBasicGroupObject } from "viewer/view/right-border-tabs/trees_tab/tree_hierarchy_view_helpers";
22+
import { afterAll, describe, expect, test } from "vitest";
23+
24+
const enforceVolumeTracing = (state: WebknossosState) => {
25+
const tracing = state.annotation.volumes[0];
26+
if (tracing == null || state.annotation.volumes.length !== 1) {
27+
throw new Error("No volume tracing found");
28+
}
29+
return tracing;
30+
};
31+
32+
const initialState: WebknossosState = update(defaultVolumeState, {
33+
annotation: {
34+
restrictions: {
35+
allowUpdate: {
36+
$set: true,
37+
},
38+
branchPointsAllowed: {
39+
$set: true,
40+
},
41+
},
42+
annotationType: { $set: "Explorational" },
43+
},
44+
dataset: {
45+
dataSource: {
46+
dataLayers: {
47+
$set: [sampleTracingLayer],
48+
},
49+
},
50+
},
51+
});
52+
53+
const { tracingId } = initialState.annotation.volumes[0];
54+
55+
const applyActions = chainReduce(combinedReducer);
56+
57+
// This helper dict exists so that we can ensure via typescript that
58+
// the list contains all members of ApplicableVolumeUpdateAction. As soon as
59+
// ApplicableVolumeUpdateAction is extended with another action, TS will complain
60+
// if the following dictionary doesn't contain that action.
61+
const actionNamesHelper: Record<ApplicableVolumeUpdateAction["name"], true> = {
62+
updateLargestSegmentId: true,
63+
updateSegment: true,
64+
createSegment: true,
65+
deleteSegment: true,
66+
updateSegmentGroups: true,
67+
addUserBoundingBoxInVolumeTracing: true,
68+
updateUserBoundingBoxInVolumeTracing: true,
69+
deleteUserBoundingBoxInVolumeTracing: true,
70+
updateSegmentGroupsExpandedState: true,
71+
updateUserBoundingBoxVisibilityInVolumeTracing: true,
72+
};
73+
const actionNamesList = Object.keys(actionNamesHelper);
74+
75+
describe("Update Action Application for VolumeTracing", () => {
76+
const seenActionTypes = new Set<string>();
77+
78+
/*
79+
* Hardcode these values if you want to focus on a specific test.
80+
*/
81+
const compactionModes = [true, false];
82+
const hardcodedBeforeVersionIndex: number | null = null;
83+
const hardcodedAfterVersionIndex: number | null = null;
84+
85+
const userActions: Action[] = [
86+
VolumeTracingActions.updateSegmentAction(2, { somePosition: [1, 2, 3] }, tracingId),
87+
VolumeTracingActions.updateSegmentAction(3, { somePosition: [3, 4, 5] }, tracingId),
88+
VolumeTracingActions.updateSegmentAction(
89+
3,
90+
{
91+
name: "name",
92+
groupId: 3,
93+
metadata: [
94+
{
95+
key: "someKey",
96+
stringValue: "some string value",
97+
},
98+
],
99+
},
100+
tracingId,
101+
),
102+
addUserBoundingBoxAction({
103+
boundingBox: { min: [0, 0, 0], max: [10, 10, 10] },
104+
name: "UserBBox",
105+
color: [1, 2, 3],
106+
isVisible: true,
107+
}),
108+
changeUserBoundingBoxAction(1, { name: "Updated Name" }),
109+
deleteUserBoundingBoxAction(1),
110+
VolumeTracingActions.setSegmentGroupsAction(
111+
[makeBasicGroupObject(3, "group 3"), makeBasicGroupObject(7, "group 7")],
112+
tracingId,
113+
),
114+
VolumeTracingActions.removeSegmentAction(3, tracingId),
115+
VolumeTracingActions.setLargestSegmentIdAction(10000),
116+
];
117+
118+
test("User actions for test should not contain no-ops", () => {
119+
let state = initialState;
120+
for (const action of userActions) {
121+
const newState = combinedReducer(state, action);
122+
expect(newState !== state).toBeTruthy();
123+
124+
state = newState;
125+
}
126+
});
127+
128+
const beforeVersionIndices =
129+
hardcodedBeforeVersionIndex != null
130+
? [hardcodedBeforeVersionIndex]
131+
: _.range(0, userActions.length);
132+
133+
describe.each(compactionModes)(
134+
"[Compaction=%s]: should re-apply update actions from complex diff and get same state",
135+
(withCompaction) => {
136+
describe.each(beforeVersionIndices)("From v=%i", (beforeVersionIndex: number) => {
137+
const afterVersionIndices =
138+
hardcodedAfterVersionIndex != null
139+
? [hardcodedAfterVersionIndex]
140+
: _.range(beforeVersionIndex, userActions.length + 1);
141+
142+
test.each(afterVersionIndices)("To v=%i", (afterVersionIndex: number) => {
143+
const state2WithActiveTree = applyActions(
144+
initialState,
145+
userActions.slice(0, beforeVersionIndex),
146+
);
147+
148+
const state2WithoutActiveState = applyActions(state2WithActiveTree, [
149+
VolumeTracingActions.setActiveCellAction(0),
150+
setActiveUserBoundingBoxId(null),
151+
]);
152+
153+
const actionsToApply = userActions.slice(beforeVersionIndex, afterVersionIndex + 1);
154+
const state3 = applyActions(
155+
state2WithActiveTree,
156+
actionsToApply.concat([
157+
VolumeTracingActions.setActiveCellAction(0),
158+
setActiveUserBoundingBoxId(null),
159+
]),
160+
);
161+
expect(state2WithoutActiveState !== state3).toBeTruthy();
162+
163+
const volumeTracing2 = enforceVolumeTracing(state2WithoutActiveState);
164+
const volumeTracing3 = enforceVolumeTracing(state3);
165+
166+
const updateActionsBeforeCompaction = Array.from(
167+
diffVolumeTracing(volumeTracing2, volumeTracing3),
168+
);
169+
console.log(
170+
"updateActionsBeforeCompaction",
171+
updateActionsBeforeCompaction.map((el) => el.name),
172+
);
173+
const maybeCompact = withCompaction
174+
? compactUpdateActions
175+
: (updateActions: UpdateActionWithoutIsolationRequirement[]) => updateActions;
176+
const updateActions = maybeCompact(
177+
updateActionsBeforeCompaction,
178+
volumeTracing2,
179+
volumeTracing3,
180+
) as ApplicableVolumeUpdateAction[];
181+
182+
for (const action of updateActions) {
183+
seenActionTypes.add(action.name);
184+
}
185+
186+
const reappliedNewState = applyActions(state2WithoutActiveState, [
187+
VolumeTracingActions.applyVolumeUpdateActionsFromServerAction(updateActions),
188+
VolumeTracingActions.setActiveCellAction(0),
189+
setActiveUserBoundingBoxId(null),
190+
]);
191+
192+
expect(reappliedNewState).toEqual(state3);
193+
});
194+
});
195+
},
196+
);
197+
198+
afterAll(() => {
199+
expect(seenActionTypes).toEqual(new Set(actionNamesList));
200+
});
201+
});

frontend/javascripts/viewer/model/reducers/update_action_application/bounding_box.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function handleUserBoundingBoxUpdateInTracing(
8484
tracing.tracingId === volumeTracing.tracingId
8585
? {
8686
...volumeTracing,
87-
updatedUserBoundingBoxes,
87+
userBoundingBoxes: updatedUserBoundingBoxes,
8888
}
8989
: volumeTracing,
9090
);

frontend/javascripts/viewer/model/reducers/update_action_application/volume.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
updateSegmentAction,
66
} from "viewer/model/actions/volumetracing_actions";
77
import type { ApplicableVolumeUpdateAction } from "viewer/model/sagas/update_actions";
8-
import type { WebknossosState } from "viewer/store";
8+
import type { Segment, WebknossosState } from "viewer/store";
99
import type { VolumeTracingReducerAction } from "../volumetracing_reducer";
1010
import { setLargestSegmentIdReducer } from "../volumetracing_reducer_helpers";
1111
import {
@@ -36,10 +36,15 @@ export function applyVolumeUpdateActionsFromServer(
3636
}
3737
case "createSegment":
3838
case "updateSegment": {
39-
const { actionTracingId, ...segment } = ua.value;
39+
const { actionTracingId, ...originalSegment } = ua.value;
40+
const { anchorPosition, ...segmentWithoutAnchor } = originalSegment;
41+
const segment: Partial<Segment> = {
42+
somePosition: anchorPosition ?? undefined,
43+
...segmentWithoutAnchor,
44+
};
4045
newState = VolumeTracingReducer(
4146
newState,
42-
updateSegmentAction(segment.id, segment, actionTracingId),
47+
updateSegmentAction(originalSegment.id, segment, actionTracingId),
4348
);
4449
break;
4550
}
@@ -81,6 +86,12 @@ export function applyVolumeUpdateActionsFromServer(
8186
);
8287
break;
8388
}
89+
case "updateSegmentGroupsExpandedState":
90+
case "updateUserBoundingBoxVisibilityInVolumeTracing": {
91+
// These update actions are user specific and don't need to be incorporated here
92+
// because they are from another user.
93+
break;
94+
}
8495
default: {
8596
ua satisfies never;
8697
}

frontend/javascripts/viewer/model/sagas/update_actions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ export type ApplicableVolumeUpdateAction =
131131
| UpdateSegmentGroupsUpdateAction
132132
| AddUserBoundingBoxInVolumeTracingAction
133133
| UpdateUserBoundingBoxInVolumeTracingAction
134-
| DeleteUserBoundingBoxInVolumeTracingAction;
134+
| DeleteUserBoundingBoxInVolumeTracingAction
135+
| UpdateSegmentGroupsExpandedStateUpdateAction
136+
| UpdateUserBoundingBoxVisibilityInVolumeTracingAction;
135137

136138
export type UpdateActionWithIsolationRequirement =
137139
| RevertToVersionUpdateAction

0 commit comments

Comments
 (0)