Skip to content

Commit cd1bab2

Browse files
committed
live update to proofreading actions
1 parent fc7c242 commit cd1bab2

File tree

4 files changed

+155
-41
lines changed

4 files changed

+155
-41
lines changed

frontend/javascripts/viewer/model/reducers/volumetracing_reducer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import type {
2424
SetMappingNameAction,
2525
} from "viewer/model/actions/settings_actions";
2626
import {
27-
removeSegmentAction,
28-
updateSegmentAction,
2927
type ClickSegmentAction,
3028
type RemoveSegmentAction,
3129
type SetSegmentsAction,
3230
type UpdateSegmentAction,
3331
type VolumeTracingAction,
32+
removeSegmentAction,
33+
updateSegmentAction,
3434
} from "viewer/model/actions/volumetracing_actions";
3535
import { updateKey2 } from "viewer/model/helpers/deep_update";
3636
import {
@@ -714,6 +714,7 @@ function VolumeTracingReducer(
714714
}
715715
}
716716
}
717+
break;
717718
}
718719

719720
default:

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ function* watchChangedBucketsForLayer(layerName: string): Saga<never> {
257257
const mappingInfo = yield* select((state) =>
258258
getMappingInfo(state.temporaryConfiguration.activeMappingByLayer, layerName),
259259
);
260-
const { mappingName, mappingType, mappingStatus } = mappingInfo;
260+
const { mappingName, mappingStatus } = mappingInfo;
261261

262262
if (mappingName == null || mappingStatus !== MappingStatusEnum.ENABLED) {
263263
return;
@@ -271,7 +271,7 @@ function* watchChangedBucketsForLayer(layerName: string): Saga<never> {
271271
let isBusy = yield* select((state) => state.uiInformation.busyBlockingInfo.isBusy);
272272
if (!isBusy) {
273273
const { cancel } = yield* race({
274-
updateHdf5: call(updateLocalHdf5Mapping, layerName, layerInfo, mappingName, mappingType),
274+
updateHdf5: call(updateLocalHdf5Mapping, layerName, layerInfo, mappingName),
275275
cancel: take(
276276
((action: Action) =>
277277
action.type === "SET_BUSY_BLOCKING_INFO_ACTION" &&
@@ -397,7 +397,6 @@ function* handleSetMapping(
397397
layerName,
398398
layerInfo,
399399
mappingName,
400-
mappingType,
401400
action,
402401
oldActiveMappingByLayer,
403402
);
@@ -408,23 +407,21 @@ function* handleSetHdf5Mapping(
408407
layerName: string,
409408
layerInfo: APIDataLayer,
410409
mappingName: string,
411-
mappingType: MappingType,
412410
action: SetMappingAction,
413411
oldActiveMappingByLayer: Container<Record<string, ActiveMappingInfo>>,
414412
): Saga<void> {
415413
if (yield* select((state) => getNeedsLocalHdf5Mapping(state, layerName))) {
416-
yield* call(updateLocalHdf5Mapping, layerName, layerInfo, mappingName, mappingType);
414+
yield* call(updateLocalHdf5Mapping, layerName, layerInfo, mappingName);
417415
} else {
418416
// An HDF5 mapping was set that is applied remotely. A reload is necessary.
419417
yield* call(reloadData, oldActiveMappingByLayer, action);
420418
}
421419
}
422420

423-
function* updateLocalHdf5Mapping(
421+
export function* updateLocalHdf5Mapping(
424422
layerName: string,
425423
layerInfo: APIDataLayer,
426424
mappingName: string,
427-
mappingType: MappingType,
428425
): Saga<void> {
429426
const dataset = yield* select((state) => state.dataset);
430427
const annotation = yield* select((state) => state.annotation);
@@ -490,7 +487,7 @@ function* updateLocalHdf5Mapping(
490487
onlyB: newSegmentIds,
491488
});
492489

493-
yield* put(setMappingAction(layerName, mappingName, mappingType, { mapping }));
490+
yield* put(setMappingAction(layerName, mappingName, "HDF5", { mapping }));
494491
}
495492

496493
function* handleSetJsonMapping(

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

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -395,17 +395,13 @@ function* handleSkeletonProofreadingAction(action: Action): Saga<void> {
395395
volumeTracingId,
396396
),
397397
);
398-
const mergedMapping = yield* call(
399-
mergeAgglomeratesInMapping,
398+
yield* call(
399+
updateMappingWithMerge,
400+
volumeTracingId,
400401
activeMapping,
401402
targetAgglomerateId,
402403
sourceAgglomerateId,
403404
);
404-
yield* put(
405-
setMappingAction(volumeTracingId, activeMapping.mappingName, activeMapping.mappingType, {
406-
mapping: mergedMapping,
407-
}),
408-
);
409405
} else if (action.type === "DELETE_EDGE") {
410406
if (sourceAgglomerateId !== targetAgglomerateId) {
411407
Toast.error("Segments that should be split need to be in the same agglomerate.");
@@ -740,18 +736,13 @@ function* handleProofreadMergeOrMinCut(action: Action) {
740736
sourceInfo.unmappedId,
741737
targetInfo.unmappedId,
742738
);
743-
const mergedMapping = yield* call(
744-
mergeAgglomeratesInMapping,
739+
yield* call(
740+
updateMappingWithMerge,
741+
volumeTracingId,
745742
activeMapping,
746743
targetAgglomerateId,
747744
sourceAgglomerateId,
748745
);
749-
750-
yield* put(
751-
setMappingAction(volumeTracingId, activeMapping.mappingName, activeMapping.mappingType, {
752-
mapping: mergedMapping,
753-
}),
754-
);
755746
} else if (action.type === "MIN_CUT_AGGLOMERATE") {
756747
if (sourceInfo.unmappedId === targetInfo.unmappedId) {
757748
Toast.error(
@@ -1248,10 +1239,9 @@ function* getPositionForSegmentId(volumeTracing: VolumeTracing, segmentId: numbe
12481239
return position;
12491240
}
12501241

1251-
function* splitAgglomerateInMapping(
1242+
function getSegmentIdsThatMapToAgglomerate(
12521243
activeMapping: ActiveMappingInfo,
12531244
sourceAgglomerateId: number,
1254-
volumeTracingId: string,
12551245
) {
12561246
// Obtain all segment ids that map to sourceAgglomerateId
12571247
const mappingEntries = Array.from(activeMapping.mapping as NumberLikeMap);
@@ -1263,10 +1253,17 @@ function* splitAgglomerateInMapping(
12631253

12641254
// If the mapping contains BigInts, we need a BigInt for the filtering
12651255
const comparableSourceAgglomerateId = adaptToType(sourceAgglomerateId);
1266-
const splitSegmentIds = mappingEntries
1256+
return mappingEntries
12671257
.filter(([_segmentId, agglomerateId]) => agglomerateId === comparableSourceAgglomerateId)
12681258
.map(([segmentId, _agglomerateId]) => segmentId);
1259+
}
12691260

1261+
function* splitAgglomerateInMapping(
1262+
activeMapping: ActiveMappingInfo,
1263+
sourceAgglomerateId: number,
1264+
volumeTracingId: string,
1265+
) {
1266+
const splitSegmentIds = getSegmentIdsThatMapToAgglomerate(activeMapping, sourceAgglomerateId);
12701267
const annotationId = yield* select((state) => state.annotation.annotationId);
12711268
const tracingStoreUrl = yield* select((state) => state.annotation.tracingStore.url);
12721269
// Ask the server to map the (split) segment ids. This creates a partial mapping
@@ -1313,6 +1310,61 @@ function mergeAgglomeratesInMapping(
13131310
) as Mapping;
13141311
}
13151312

1313+
export function* updateMappingWithMerge(
1314+
volumeTracingId: string,
1315+
activeMapping: ActiveMappingInfo,
1316+
targetAgglomerateId: number,
1317+
sourceAgglomerateId: number,
1318+
) {
1319+
const mergedMapping = yield* call(
1320+
mergeAgglomeratesInMapping,
1321+
activeMapping,
1322+
targetAgglomerateId,
1323+
sourceAgglomerateId,
1324+
);
1325+
yield* put(
1326+
setMappingAction(volumeTracingId, activeMapping.mappingName, activeMapping.mappingType, {
1327+
mapping: mergedMapping,
1328+
}),
1329+
);
1330+
}
1331+
1332+
export function* updateMappingWithOmittedSplitPartners(
1333+
volumeTracingId: string,
1334+
activeMapping: ActiveMappingInfo,
1335+
sourceAgglomerateId: number,
1336+
) {
1337+
/*
1338+
* sourceAgglomerateId was split. All segment ids that were mapped to sourceAgglomerateId,
1339+
* are removed from the activeMapping by this function.
1340+
* The return value of this function is the list of segment ids that were removed.
1341+
*/
1342+
1343+
const mappingEntries = Array.from(activeMapping.mapping as NumberLikeMap);
1344+
1345+
const adaptToType =
1346+
mappingEntries.length > 0 && isBigInt(mappingEntries[0][0])
1347+
? (el: number) => BigInt(el)
1348+
: (el: number) => el;
1349+
// If the mapping contains BigInts, we need a BigInt for the filtering
1350+
const comparableSourceAgglomerateId = adaptToType(sourceAgglomerateId);
1351+
1352+
const newMapping = new Map();
1353+
1354+
for (const entry of mappingEntries) {
1355+
const [key, value] = entry;
1356+
if (value !== comparableSourceAgglomerateId) {
1357+
newMapping.set(key, value);
1358+
}
1359+
}
1360+
1361+
yield* put(
1362+
setMappingAction(volumeTracingId, activeMapping.mappingName, activeMapping.mappingType, {
1363+
mapping: newMapping,
1364+
}),
1365+
);
1366+
}
1367+
13161368
function* gatherInfoForOperation(
13171369
action: ProofreadMergeAction | MinCutAgglomerateWithPositionAction,
13181370
preparation: Preparation,

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

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import messages from "messages";
1212
import { call, delay, fork, put, race, take, takeEvery } from "typed-redux-saga";
1313
import type { APIUpdateActionBatch } from "types/api_types";
1414
import { ControlModeEnum } from "viewer/constants";
15-
import { getMagInfo } from "viewer/model/accessors/dataset_accessor";
15+
import {
16+
getLayerByName,
17+
getMagInfo,
18+
getMappingInfo,
19+
} from "viewer/model/accessors/dataset_accessor";
1620
import { selectTracing } from "viewer/model/accessors/tracing_accessor";
1721
import { FlycamActions } from "viewer/model/actions/flycam_actions";
1822
import {
@@ -29,9 +33,9 @@ import {
2933
} from "viewer/model/actions/skeletontracing_actions";
3034
import { ViewModeSaveRelevantActions } from "viewer/model/actions/view_mode_actions";
3135
import {
32-
applyVolumeUpdateActionsFromServerAction,
3336
type InitializeVolumeTracingAction,
3437
VolumeTracingSaveRelevantActions,
38+
applyVolumeUpdateActionsFromServerAction,
3539
} from "viewer/model/actions/volumetracing_actions";
3640
import compactSaveQueue from "viewer/model/helpers/compaction/compact_save_queue";
3741
import compactUpdateActions from "viewer/model/helpers/compaction/compact_update_actions";
@@ -65,6 +69,8 @@ import type {
6569
} from "viewer/store";
6670
import { getFlooredPosition, getRotation } from "../accessors/flycam_accessor";
6771
import type { BatchedAnnotationInitializationAction } from "../actions/annotation_actions";
72+
import { updateLocalHdf5Mapping } from "./mapping_saga";
73+
import { updateMappingWithMerge, updateMappingWithOmittedSplitPartners } from "./proofread_saga";
6874
import { takeEveryWithBatchActionSupport } from "./saga_helpers";
6975

7076
const ONE_YEAR_MS = 365 * 24 * 3600 * 1000;
@@ -490,7 +496,7 @@ const VERSION_POLL_INTERVAL_READ_ONLY = 1 * 1000;
490496
const VERSION_POLL_INTERVAL_SINGLE_EDITOR = 1 * 1000;
491497

492498
function* watchForSaveConflicts(): Saga<never> {
493-
function* checkForNewVersion(): boolean {
499+
function* checkForNewVersion(): Saga<boolean> {
494500
/*
495501
* Checks whether there is a newer version on the server. If so,
496502
* the saga tries to also update the current annotation to the newest
@@ -573,8 +579,7 @@ function* watchForSaveConflicts(): Saga<never> {
573579

574580
console.log("newerActions", newerActions);
575581

576-
if (yield* canActionsBeIncorporated(newerActions)) {
577-
yield* put(setVersionNumberAction(versionOnServer));
582+
if (yield* tryToIncorporateActions(newerActions)) {
578583
return false;
579584
}
580585

@@ -641,12 +646,18 @@ function* watchForSaveConflicts(): Saga<never> {
641646
// @ts-ignore
642647
ErrorHandling.notify(exception);
643648
// todop: remove again?
644-
Toast.error(exception);
649+
Toast.error(`${exception}`);
645650
}
646651
}
647652
}
648653

649-
function* canActionsBeIncorporated(newerActions: APIUpdateActionBatch[]): Saga<boolean> {
654+
function* tryToIncorporateActions(newerActions: APIUpdateActionBatch[]): Saga<boolean> {
655+
const refreshFunctionByTracing: Record<string, () => Saga<void>> = {};
656+
function* finalize() {
657+
for (const fn of Object.values(refreshFunctionByTracing)) {
658+
yield* call(fn);
659+
}
660+
}
650661
for (const actionBatch of newerActions) {
651662
for (const action of actionBatch.value) {
652663
switch (action.name) {
@@ -712,6 +723,60 @@ function* canActionsBeIncorporated(newerActions: APIUpdateActionBatch[]): Saga<b
712723
break;
713724
}
714725

726+
// Proofreading
727+
case "mergeAgglomerate": {
728+
const activeMapping = yield* select(
729+
(store) =>
730+
store.temporaryConfiguration.activeMappingByLayer[action.value.actionTracingId],
731+
);
732+
yield* call(
733+
updateMappingWithMerge,
734+
action.value.actionTracingId,
735+
activeMapping,
736+
action.value.agglomerateId2,
737+
action.value.agglomerateId1,
738+
);
739+
break;
740+
}
741+
case "splitAgglomerate": {
742+
const activeMapping = yield* select(
743+
(store) =>
744+
store.temporaryConfiguration.activeMappingByLayer[action.value.actionTracingId],
745+
);
746+
yield* call(
747+
updateMappingWithOmittedSplitPartners,
748+
action.value.actionTracingId,
749+
activeMapping,
750+
action.value.agglomerateId,
751+
);
752+
753+
const layerName = action.value.actionTracingId;
754+
755+
const mappingInfo = yield* select((state) =>
756+
getMappingInfo(state.temporaryConfiguration.activeMappingByLayer, layerName),
757+
);
758+
const { mappingName } = mappingInfo;
759+
760+
if (mappingName == null) {
761+
throw new Error(
762+
"Could not apply splitAgglomerate because no active mapping was found.",
763+
);
764+
}
765+
766+
const dataset = yield* select((state) => state.dataset);
767+
const layerInfo = getLayerByName(dataset, layerName);
768+
769+
refreshFunctionByTracing[layerName] = function* (): Saga<void> {
770+
yield* call(updateLocalHdf5Mapping, layerName, layerInfo, mappingName);
771+
};
772+
773+
break;
774+
}
775+
776+
/*
777+
* Currently not supported:
778+
*/
779+
715780
// High-level annotation specific
716781
case "addLayerToAnnotation":
717782
case "addSegmentIndex":
@@ -725,13 +790,9 @@ function* canActionsBeIncorporated(newerActions: APIUpdateActionBatch[]): Saga<b
725790
// Volume
726791
case "removeFallbackLayer":
727792
case "updateSegmentGroups":
728-
case "updateUserBoundingBoxesInVolumeTracing":
793+
case "updateUserBoundingBoxesInVolumeTracing": // Wait for #8492 first.
729794
case "updateMappingName": // Refactor mapping activation first before implementing this.
730795

731-
// Proofreading
732-
case "mergeAgglomerate":
733-
case "splitAgglomerate":
734-
735796
// Skeleton
736797
case "createTree":
737798
case "deleteEdge":
@@ -744,18 +805,21 @@ function* canActionsBeIncorporated(newerActions: APIUpdateActionBatch[]): Saga<b
744805
case "updateTreeGroups":
745806
case "moveTreeComponent":
746807
case "updateNode":
747-
case "updateUserBoundingBoxesInSkeletonTracing":
808+
case "updateUserBoundingBoxesInSkeletonTracing": // Wait for #8492 first.
748809

749810
case "updateVolumeTracing": {
750811
console.log("cannot apply action", action.name);
812+
yield* call(finalize);
751813
return false;
752814
}
753815
default: {
754816
action satisfies never;
755817
}
756818
}
757819
}
820+
yield* put(setVersionNumberAction(actionBatch.version));
758821
}
822+
yield* call(finalize);
759823
return true;
760824
}
761825

0 commit comments

Comments
 (0)