Skip to content
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

Add volume undo/redo support #4771

Merged
merged 28 commits into from
Aug 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
88af051
fix typo
MichaelBuessemeyer Aug 13, 2020
76357d0
WIP: Adding volume annotation in batches to undo stack
MichaelBuessemeyer Aug 13, 2020
f2d4f0d
first version with undo
MichaelBuessemeyer Aug 14, 2020
defa7aa
added first version of volume undo, not working correctly :/
MichaelBuessemeyer Aug 17, 2020
94d3b4a
I think undo works now
MichaelBuessemeyer Aug 18, 2020
72975e8
clean up
MichaelBuessemeyer Aug 18, 2020
d5b6c65
small fix
MichaelBuessemeyer Aug 18, 2020
c0b55dd
Merge branch 'master' into add-volume-undo
MichaelBuessemeyer Aug 18, 2020
da7f7fd
fix tests and adjust naming of worker
MichaelBuessemeyer Aug 18, 2020
373442a
Merge branch 'add-volume-undo' of github.com:scalableminds/webknossos…
MichaelBuessemeyer Aug 18, 2020
5182358
add comment
MichaelBuessemeyer Aug 18, 2020
e5ebdc8
add type annotation
MichaelBuessemeyer Aug 18, 2020
30a6ba8
fix flow
MichaelBuessemeyer Aug 18, 2020
9bca52f
add better flow defintion for saga effect join
MichaelBuessemeyer Aug 19, 2020
d58e285
apply feedback
MichaelBuessemeyer Aug 20, 2020
6240fd3
Merge branch 'master' of github.com:scalableminds/webknossos into add…
MichaelBuessemeyer Aug 20, 2020
9a0023c
remove useless type cast & fix typo
MichaelBuessemeyer Aug 20, 2020
83a1467
various fixes
MichaelBuessemeyer Aug 20, 2020
5c1a7e2
pretty flow file
MichaelBuessemeyer Aug 20, 2020
eb4e099
fix tests
MichaelBuessemeyer Aug 20, 2020
bf83dcb
Merge branch 'master' into add-volume-undo
MichaelBuessemeyer Aug 20, 2020
e64a7af
fix linting
MichaelBuessemeyer Aug 20, 2020
c8813f3
apply feedback
MichaelBuessemeyer Aug 21, 2020
3081000
cache node radius to avoid clearing the redo stack
MichaelBuessemeyer Aug 21, 2020
06378fb
Merge branch 'master' into add-volume-undo
MichaelBuessemeyer Aug 21, 2020
3d61885
fix merge
MichaelBuessemeyer Aug 21, 2020
8a27a7f
undo node radius fix && only clear redo stack if a relevant action i…
MichaelBuessemeyer Aug 24, 2020
dd89a32
add changelog entry
MichaelBuessemeyer Aug 24, 2020
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
9 changes: 8 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"trailingComma": "all",
"printWidth": 100,
"overrides": [
{
"files": "effect-generators.js.flow",
"options": {
"printWidth": 200
}
}
]
}

1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Added
- Added the possibility to remove the fallback segmentation layer from a hybrid/volume tracing. Accessible by a minus button next to the layer's settings. [#4741](https://github.com/scalableminds/webknossos/pull/4766)
- Added the possibility to undo and redo volume annotation strokes. [#4771](https://github.com/scalableminds/webknossos/pull/4771)

### Changed
-
Expand Down
2 changes: 2 additions & 0 deletions frontend/javascripts/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ instead. Only enable this option if you understand its effect. All layers will n
"tracing.branchpoint_set": "Branchpoint set",
"tracing.branchpoint_jump_twice":
"You didn't add a node after jumping to this branchpoint, do you really want to jump again?",
"tracing.edit_volume_in_merger_mode":
"The volume annotation would be changed by this action. This is not allowed while merger mode is active.",
"tracing.segmentation_zoom_warning":
"Segmentation data and volume annotation is only fully supported at a smaller zoom level.",
"tracing.uint64_segmentation_warning":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
addToLayerAction,
finishEditingAction,
hideBrushAction,
setContourTracingMode,
setContourTracingModeAction,
cycleToolAction,
copySegmentationLayerAction,
inferSegmentationInViewportAction,
Expand Down Expand Up @@ -99,9 +99,9 @@ export function getPlaneMouseControls(_planeId: OrthoView): * {
if (isAutomaticBrushEnabled()) {
return;
}
Store.dispatch(setContourTracingMode(ContourModeEnum.DRAW));
Store.dispatch(setContourTracingModeAction(ContourModeEnum.DRAW));
} else {
Store.dispatch(setContourTracingMode(ContourModeEnum.DRAW_OVERWRITE));
Store.dispatch(setContourTracingModeAction(ContourModeEnum.DRAW_OVERWRITE));
}
Store.dispatch(startEditingAction(calculateGlobalPos(pos), plane));
}
Expand All @@ -110,7 +110,7 @@ export function getPlaneMouseControls(_planeId: OrthoView): * {
leftMouseUp: () => {
const tool = Utils.enforce(getVolumeTool)(Store.getState().tracing.volume);

Store.dispatch(setContourTracingMode(ContourModeEnum.IDLE));
Store.dispatch(setContourTracingModeAction(ContourModeEnum.IDLE));

if (tool === VolumeToolEnum.TRACE || tool === VolumeToolEnum.BRUSH) {
Store.dispatch(finishEditingAction());
Expand All @@ -137,9 +137,9 @@ export function getPlaneMouseControls(_planeId: OrthoView): * {

if (!event.shiftKey && (tool === VolumeToolEnum.TRACE || tool === VolumeToolEnum.BRUSH)) {
if (event.ctrlKey) {
Store.dispatch(setContourTracingMode(ContourModeEnum.DELETE_FROM_ANY_CELL));
Store.dispatch(setContourTracingModeAction(ContourModeEnum.DELETE_FROM_ANY_CELL));
} else {
Store.dispatch(setContourTracingMode(ContourModeEnum.DELETE_FROM_ACTIVE_CELL));
Store.dispatch(setContourTracingModeAction(ContourModeEnum.DELETE_FROM_ACTIVE_CELL));
}
Store.dispatch(startEditingAction(calculateGlobalPos(pos), plane));
}
Expand All @@ -148,11 +148,11 @@ export function getPlaneMouseControls(_planeId: OrthoView): * {
rightMouseUp: () => {
const tool = Utils.enforce(getVolumeTool)(Store.getState().tracing.volume);

Store.dispatch(setContourTracingMode(ContourModeEnum.IDLE));
Store.dispatch(setContourTracingModeAction(ContourModeEnum.IDLE));

if (tool === VolumeToolEnum.TRACE || tool === VolumeToolEnum.BRUSH) {
Store.dispatch(finishEditingAction());
Store.dispatch(setContourTracingMode(ContourModeEnum.IDLE));
Store.dispatch(setContourTracingModeAction(ContourModeEnum.IDLE));
}
},

Expand Down
4 changes: 2 additions & 2 deletions frontend/javascripts/oxalis/model/actions/save_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ type SetVersionNumberAction = {
version: number,
tracingType: Tracing,
};
type UndoAction = { type: "UNDO" };
type RedoAction = { type: "REDO" };
export type UndoAction = { type: "UNDO" };
export type RedoAction = { type: "REDO" };
type DisableSavingAction = { type: "DISABLE_SAVING" };
export type SaveAction =
| PushSaveQueueTransaction
Expand Down
35 changes: 31 additions & 4 deletions frontend/javascripts/oxalis/model/actions/volumetracing_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
* @flow
*/
import type { ServerVolumeTracing } from "admin/api_flow_types";
import type { Vector2, Vector3, OrthoView, VolumeTool, ContourMode } from "oxalis/constants";
import type {
Vector2,
Vector3,
Vector4,
OrthoView,
VolumeTool,
ContourMode,
} from "oxalis/constants";
import type { BucketDataArray } from "oxalis/model/bucket_data_handling/bucket";

type InitializeVolumeTracingAction = {
type: "INITIALIZE_VOLUMETRACING",
Expand All @@ -20,11 +28,17 @@ export type CopySegmentationLayerAction = {
type: "COPY_SEGMENTATION_LAYER",
source: "previousLayer" | "nextLayer",
};
export type AddBucketToUndoAction = {
type: "ADD_BUCKET_TO_UNDO",
zoomedBucketAddress: Vector4,
bucketData: BucketDataArray,
};
type UpdateDirectionAction = { type: "UPDATE_DIRECTION", centroid: Vector3 };
type ResetContourAction = { type: "RESET_CONTOUR" };
export type FinishAnnotationStrokeAction = { type: "FINISH_ANNOTATION_STROKE" };
type SetMousePositionAction = { type: "SET_MOUSE_POSITION", position: Vector2 };
type HideBrushAction = { type: "HIDE_BRUSH" };
type SetContourTracingMode = { type: "SET_CONTOUR_TRACING_MODE", mode: ContourMode };
type SetContourTracingModeAction = { type: "SET_CONTOUR_TRACING_MODE", mode: ContourMode };
export type InferSegmentationInViewportAction = {
type: "INFER_SEGMENT_IN_VIEWPORT",
position: Vector3,
Expand All @@ -42,11 +56,13 @@ export type VolumeTracingAction =
| CycleToolAction
| UpdateDirectionAction
| ResetContourAction
| FinishAnnotationStrokeAction
| SetMousePositionAction
| HideBrushAction
| CopySegmentationLayerAction
| InferSegmentationInViewportAction
| SetContourTracingMode
| SetContourTracingModeAction
| AddBucketToUndoAction
| RemoveFallbackLayerAction;

export const VolumeTracingSaveRelevantActions = [
Expand All @@ -57,6 +73,8 @@ export const VolumeTracingSaveRelevantActions = [
"REMOVE_FALLBACK_LAYER",
];

export const VolumeTracingUndoRelevantActions = ["START_EDITING", "COPY_SEGMENTATION_LAYER"];

export const initializeVolumeTracingAction = (
tracing: ServerVolumeTracing,
): InitializeVolumeTracingAction => ({
Expand Down Expand Up @@ -112,6 +130,10 @@ export const resetContourAction = (): ResetContourAction => ({
type: "RESET_CONTOUR",
});

export const finishAnnotationStrokeAction = (): FinishAnnotationStrokeAction => ({
type: "FINISH_ANNOTATION_STROKE",
});

export const setMousePositionAction = (position: Vector2): SetMousePositionAction => ({
type: "SET_MOUSE_POSITION",
position,
Expand All @@ -121,11 +143,16 @@ export const hideBrushAction = (): HideBrushAction => ({
type: "HIDE_BRUSH",
});

export const setContourTracingMode = (mode: ContourMode): SetContourTracingMode => ({
export const setContourTracingModeAction = (mode: ContourMode): SetContourTracingModeAction => ({
type: "SET_CONTOUR_TRACING_MODE",
mode,
});

export const addBucketToUndoAction = (
zoomedBucketAddress: Vector4,
bucketData: BucketDataArray,
): AddBucketToUndoAction => ({ type: "ADD_BUCKET_TO_UNDO", zoomedBucketAddress, bucketData });

export const inferSegmentationInViewportAction = (
position: Vector3,
): InferSegmentationInViewportAction => ({
Expand Down
27 changes: 25 additions & 2 deletions frontend/javascripts/oxalis/model/bucket_data_handling/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import TemporalBucketManager from "oxalis/model/bucket_data_handling/temporal_bu
import Constants, { type Vector4 } from "oxalis/constants";
import window from "libs/window";
import { type ElementClass } from "admin/api_flow_types";
import { addBucketToUndoAction } from "oxalis/model/actions/volumetracing_actions";

export const BucketStateEnum = {
UNREQUESTED: "UNREQUESTED",
Expand All @@ -28,6 +29,9 @@ export const BucketStateEnum = {
};
export type BucketStateEnumType = $Keys<typeof BucketStateEnum>;
export type BucketDataArray = Uint8Array | Uint16Array | Uint32Array | Float32Array;
// This set saves whether a bucket is already added to the current undo volume batch
// and gets cleared by the save saga after an annotation step has finished.
export const bucketsAlreadyInUndoState: Set<string> = new Set();

export const bucketDebuggingFlags = {
// For visualizing buckets which are passed to the GPU
Expand Down Expand Up @@ -161,7 +165,15 @@ export class DataBucket {
}

label(labelFunc: BucketDataArray => void) {
labelFunc(this.getOrCreateData());
const bucketData = this.getOrCreateData();
const zoomedAddressAsString = this.zoomedAddress.toString();
if (!bucketsAlreadyInUndoState.has(zoomedAddressAsString)) {
const TypedArrayClass = getConstructorForElementClass(this.elementClass)[0];
const dataClone = new TypedArrayClass(bucketData);
Store.dispatch(addBucketToUndoAction(this.zoomedAddress, dataClone));
bucketsAlreadyInUndoState.add(zoomedAddressAsString);
}
labelFunc(bucketData);
this.dirty = true;
this.throttledTriggerLabeled();
}
Expand All @@ -173,14 +185,25 @@ export class DataBucket {
}

getData(): BucketDataArray {
const data = this.data;
const { data } = this;
if (data == null) {
throw new Error("Bucket.getData() called, but data does not exist (anymore).");
}

return data;
}

setData(newData: Uint8Array) {
const TypedArrayClass = getConstructorForElementClass(this.elementClass)[0];
this.data = new TypedArrayClass(
newData.buffer,
newData.byteOffset,
newData.byteLength / TypedArrayClass.BYTES_PER_ELEMENT,
);
this.dirty = true;
this.trigger("bucketLabeled");
}

markAsNeeded(): void {
this.accessed = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class DataCube {
on: Function;
off: Function;

// The cube stores the buckets in a seperate array for each zoomStep. For each
// The cube stores the buckets in a separate array for each zoomStep. For each
// zoomStep the cube-array contains the boundaries and an array holding the buckets.
// The bucket-arrays are initialized large enough to hold the whole cube. Thus no
// expanding is necessary. bucketCount keeps track of how many buckets are currently
Expand Down Expand Up @@ -331,8 +331,7 @@ class DataCube {
this.labelVoxel(voxel, label, activeCellId);
}

this.pushQueue.push();
this.trigger("volumeLabeled");
this.triggerPushQueue();
}

labelVoxel(voxel: Vector3, label: number, activeCellId: ?number): void {
Expand Down Expand Up @@ -368,6 +367,20 @@ class DataCube {
}
}

setBucketData(zoomedAddress: Vector4, data: Uint8Array) {
const bucket = this.getOrCreateBucket(zoomedAddress);
if (bucket.type === "null") {
return;
}
bucket.setData(data);
this.pushQueue.insert(bucket);
}

triggerPushQueue() {
this.pushQueue.push();
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
this.trigger("volumeLabeled");
}

hasDataAtPositionAndZoomStep(voxel: Vector3, zoomStep: number = 0) {
return this.getBucket(this.positionToZoomedAddress(voxel, zoomStep)).hasData();
}
Expand Down
4 changes: 4 additions & 0 deletions frontend/javascripts/oxalis/model/sagas/effect-generators.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export function* race(...args) {
return yield IOEffects.race(...args);
}

export function* join(...args) {
return yield IOEffects.join(...args);
}

export const _take = IOEffects.take;
export const _call = IOEffects.call;
export const _takeEvery = IOEffects.takeEvery;
Expand Down
Loading