Skip to content

Commit 033b1a9

Browse files
committed
live update to proofreading actions
1 parent fda5b71 commit 033b1a9

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
@@ -23,13 +23,13 @@ import type {
2323
SetMappingNameAction,
2424
} from "viewer/model/actions/settings_actions";
2525
import {
26-
removeSegmentAction,
27-
updateSegmentAction,
2826
type ClickSegmentAction,
2927
type RemoveSegmentAction,
3028
type SetSegmentsAction,
3129
type UpdateSegmentAction,
3230
type VolumeTracingAction,
31+
removeSegmentAction,
32+
updateSegmentAction,
3333
} from "viewer/model/actions/volumetracing_actions";
3434
import { updateKey2 } from "viewer/model/helpers/deep_update";
3535
import {
@@ -713,6 +713,7 @@ function VolumeTracingReducer(
713713
}
714714
}
715715
}
716+
break;
716717
}
717718

718719
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;
@@ -489,7 +495,7 @@ const VERSION_POLL_INTERVAL_READ_ONLY = 1 * 1000;
489495
const VERSION_POLL_INTERVAL_SINGLE_EDITOR = 1 * 1000;
490496

491497
function* watchForSaveConflicts(): Saga<never> {
492-
function* checkForNewVersion(): boolean {
498+
function* checkForNewVersion(): Saga<boolean> {
493499
/*
494500
* Checks whether there is a newer version on the server. If so,
495501
* the saga tries to also update the current annotation to the newest
@@ -572,8 +578,7 @@ function* watchForSaveConflicts(): Saga<never> {
572578

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

575-
if (yield* canActionsBeIncorporated(newerActions)) {
576-
yield* put(setVersionNumberAction(versionOnServer));
581+
if (yield* tryToIncorporateActions(newerActions)) {
577582
return false;
578583
}
579584

@@ -640,12 +645,18 @@ function* watchForSaveConflicts(): Saga<never> {
640645
// @ts-ignore
641646
ErrorHandling.notify(exception);
642647
// todop: remove again?
643-
Toast.error(exception);
648+
Toast.error(`${exception}`);
644649
}
645650
}
646651
}
647652

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

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

730-
// Proofreading
731-
case "mergeAgglomerate":
732-
case "splitAgglomerate":
733-
734795
// Skeleton
735796
case "createTree":
736797
case "deleteEdge":
@@ -743,18 +804,21 @@ function* canActionsBeIncorporated(newerActions: APIUpdateActionBatch[]): Saga<b
743804
case "updateTreeGroups":
744805
case "moveTreeComponent":
745806
case "updateNode":
746-
case "updateUserBoundingBoxesInSkeletonTracing":
807+
case "updateUserBoundingBoxesInSkeletonTracing": // Wait for #8492 first.
747808

748809
case "updateVolumeTracing": {
749810
console.log("cannot apply action", action.name);
811+
yield* call(finalize);
750812
return false;
751813
}
752814
default: {
753815
action satisfies never;
754816
}
755817
}
756818
}
819+
yield* put(setVersionNumberAction(actionBatch.version));
757820
}
821+
yield* call(finalize);
758822
return true;
759823
}
760824

0 commit comments

Comments
 (0)