Skip to content

Commit

Permalink
Moving and deleting multiple trees in skeletontracing (#3457)
Browse files Browse the repository at this point in the history
* added todo-comments

* adding group selecting

* implemented selecting  and auto (de)selecting a whole group of trees

* and here are the changes

* moved selective tree/groups handling one level up in the hierarchy

* enabled dragging multiple trees

* added cursor property to deselecting icon

* added selection guard to groups and trees

* fixed selecting tree groups bug; added moving multiple trees

* fixed selecting a group bug

* added check for parent group selected

* fixed moving and visual bugs

* removed logging

* refectoring + added deleting selected trees, but not tested

* fixed deleting all selected trees and a typo

* enabled deleting groups onClick

* heavily refactoring and added comments for understanding

* Update CHANGELOG.md

* change selection keys to ctrl + left mouse

* moved selecting message to trees tab view, change coloring and fix bug occuring when moving

* added keyboard shortcut info

* added feature to documentation

* removed unused sticky mouse tool tip

* applied pr feedback; removing group selection still missing

* removed multiple group selection, deletion and moving

* applied pr feedback; no active tree while multi selection

* added modal to confirm group selection when there are active trees

* added proper corner cases handling for tree selection, resolved deleting group error

* fix scrollbars caused by wrong class

* applied feedback and changed group deletion to be recursive

* remove missing groups from trees when updating tree groups
  • Loading branch information
MichaelBuessemeyer authored and daniel-wer committed Jan 10, 2019
1 parent c623b7a commit 5b83ca0
Show file tree
Hide file tree
Showing 11 changed files with 450 additions and 53 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).

### Added

- Added the possibility to select multiple trees in skeleton tracings in the tree tab by using ctrl + left mouse. Deleting and moving trees will affect all selected trees. [#3457](https://github.com/scalableminds/webknossos/pull/3457)
- Added the possibility to specify a recommended user configuration in a task type. The recommended configuration will be shown to users when they trace a task with a different task type and the configuration can be accepted or declined. [#3466](https://github.com/scalableminds/webknossos/pull/3466)
- You can now create tracings on datasets of other organizations, provided you have access rights to the dataset (i.e. it is public). [#3533](https://github.com/scalableminds/webknossos/pull/3533)
- Datasets imported through a datastore that is marked as 'scratch' will now show a construction-like header and error message to encourage moving the datasets to a permanent storage location. [#3500](https://github.com/scalableminds/webknossos/pull/3500)
Expand Down Expand Up @@ -67,8 +68,8 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).

- Removed support to watch additional dataset directories, no longer automatically creating symbolic links to the main directory. [#3416](https://github.com/scalableminds/webknossos/pull/3416)


## [18.11.0](https://github.com/scalableminds/webknossos/releases/tag/18.11.0) - 2018-10-29

[Commits](https://github.com/scalableminds/webknossos/compare/18.10.0...18.11.0)

### Highlights
Expand Down Expand Up @@ -112,8 +113,8 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
- Fixed a bug which caused the save-button to never show success for volume tracings. [#3267](https://github.com/scalableminds/webknossos/pull/3267)
- Fixed a rendering bug which caused data to turn black sometimes when moving around. [#3409](https://github.com/scalableminds/webknossos/pull/3409)


## [18.10.0](https://github.com/scalableminds/webknossos/releases/tag/18.10.0) - 2018-09-22

[Commits](https://github.com/scalableminds/webknossos/compare/18.09.0...18.10.0)

### Highlights
Expand All @@ -133,7 +134,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
- Improved security by enabling http security headers. [#3084](https://github.com/scalableminds/webknossos/pull/3084)
- Added the possibility to write markdown in the annotation description. [#3081](https://github.com/scalableminds/webknossos/pull/3081)
- Added a view to restore any older version of a skeleton tracing. Access it through the dropdown next to the Save button. [#3194](https://github.com/scalableminds/webknossos/pull/3194)
![version-restore-highlight](https://user-images.githubusercontent.com/1702075/45428378-6842d380-b6a1-11e8-88c2-e4ffcd762cd5.png)
![version-restore-highlight](https://user-images.githubusercontent.com/1702075/45428378-6842d380-b6a1-11e8-88c2-e4ffcd762cd5.png)
- Added customizable layouting to the tracing view. [#3070](https://github.com/scalableminds/webknossos/pull/3070)
- Added the brush size to the settings on the left in volume tracing. The size can now also be adjusted by using only the keyboard. [#3126](https://github.com/scalableminds/webknossos/pull/3126)
- Added a user documentation for webKnossos [#3011](https://github.com/scalableminds/webknossos/pull/3011)
Expand Down
6 changes: 5 additions & 1 deletion app/assets/javascripts/admin/help/keyboardshortcut_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ const KeyboardShortcutView = () => {
keybinding: "Right Click Drag (3D View)",
action: "Rotate 3D View",
},
{
keybinding: "Ctrl + Left Click",
action: "Select/Unselect a tree in the trees tab",
},
];

const generalShortcuts = [
Expand Down Expand Up @@ -118,7 +122,7 @@ const KeyboardShortcutView = () => {
action: "Increase/Decrease the Move Value",
},
{
keybinding: "CTRL + Shift + F",
keybinding: "Ctrl + Shift + F",
action: "Open Tree Search (if Tree List is visible)",
},
];
Expand Down
4 changes: 4 additions & 0 deletions app/assets/javascripts/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ In order to restore the current window, a reload is necessary.`,
"tracing.delete_tree": "Do you really want to delete the whole tree?",
"tracing.delete_tree_with_initial_node":
"This tree contains the initial node. Do you really want to delete the whole tree?",
"tracing.delete_mulitple_trees": _.template(
"You have <%- countOfTrees %> trees selected, do you really want to delete all those trees?",
),
"tracing.group_deletion_message": "Do you want to delete the selected group?",
"tracing.merged": "Merging successfully done",
"tracing.merged_with_redirect":
"Merging successfully done. You will be redirected to the new annotation.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ type AddTreesAndGroupsAction = {
};
type DeleteTreeAction = { type: "DELETE_TREE", treeId?: number, timestamp: number };
type SetActiveTreeAction = { type: "SET_ACTIVE_TREE", treeId: number };
type DeselectActiveTreeAction = { type: "DESELECT_ACTIVE_TREE" };
type SetActiveGroupAction = { type: "SET_ACTIVE_GROUP", groupId: number };
type DeselectActiveGroupAction = { type: "DESELECT_ACTIVE_GROUP" };
type MergeTreesAction = { type: "MERGE_TREES", sourceNodeId: number, targetNodeId: number };
type SetTreeNameAction = { type: "SET_TREE_NAME", name: ?string, treeId: ?number };
type SelectNextTreeAction = { type: "SELECT_NEXT_TREE", forward: ?boolean };
Expand Down Expand Up @@ -110,6 +112,7 @@ export type SkeletonTracingAction =
| DeleteEdgeAction
| SetActiveNodeAction
| SetActiveGroupAction
| DeselectActiveGroupAction
| SetNodeRadiusAction
| CreateBranchPointAction
| DeleteBranchPointAction
Expand All @@ -118,6 +121,7 @@ export type SkeletonTracingAction =
| AddTreesAndGroupsAction
| DeleteTreeAction
| SetActiveTreeAction
| DeselectActiveTreeAction
| MergeTreesAction
| SetTreeNameAction
| SelectNextTreeAction
Expand Down Expand Up @@ -313,11 +317,19 @@ export const setActiveTreeAction = (treeId: number): SetActiveTreeAction => ({
treeId,
});

export const deselectActiveTreeAction = (): DeselectActiveTreeAction => ({
type: "DESELECT_ACTIVE_TREE",
});

export const setActiveGroupAction = (groupId: number): SetActiveGroupAction => ({
type: "SET_ACTIVE_GROUP",
groupId,
});

export const deselectActiveGroupAction = (): DeselectActiveGroupAction => ({
type: "DESELECT_ACTIVE_GROUP",
});

export const mergeTreesAction = (sourceNodeId: number, targetNodeId: number): MergeTreesAction => ({
type: "MERGE_TREES",
sourceNodeId,
Expand Down Expand Up @@ -418,18 +430,22 @@ export const deleteActiveNodeAsUserAction = (
);
};

// Let the user confirm the deletion of the initial node (node with id 1) of a task
function confirmDeletingInitialNode(id) {
Modal.confirm({
title: messages["tracing.delete_tree_with_initial_node"],
onOk: () => {
Store.dispatch(deleteTreeAction(id));
},
});
}

export const deleteTreeAsUserAction = (treeId?: number): NoAction => {
const state = Store.getState();
const skeletonTracing = enforceSkeletonTracing(state.tracing);
getTree(skeletonTracing, treeId).map(tree => {
if (state.task != null && tree.nodes.has(1)) {
// Let the user confirm the deletion of the initial node (node with id 1) of a task
Modal.confirm({
title: messages["tracing.delete_tree_with_initial_node"],
onOk: () => {
Store.dispatch(deleteTreeAction(treeId));
},
});
confirmDeletingInitialNode(treeId);
} else if (state.userConfiguration.hideTreeRemovalWarning) {
Store.dispatch(deleteTreeAction(treeId));
} else {
Expand All @@ -442,3 +458,17 @@ export const deleteTreeAsUserAction = (treeId?: number): NoAction => {
// if the user confirms
return noAction();
};

export const deleteMultipleTreesAsUserAction = (treeIds: Array<number>): NoAction => {
const state = Store.getState();
const skeletonTracing = enforceSkeletonTracing(state.tracing);
treeIds.forEach(id => {
const tree = skeletonTracing.trees[id];
if (state.task != null && tree.nodes.has(1)) {
confirmDeletingInitialNode(id);
} else {
Store.dispatch(deleteTreeAction(id));
}
});
return noAction();
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
toggleTreeGroupReducer,
addTreesAndGroups,
createTreeMapFromTreeArray,
removeMissingGroupsFromTrees,
} from "oxalis/model/reducers/skeletontracing_reducer_helpers";
import {
getSkeletonTracing,
Expand Down Expand Up @@ -353,6 +354,17 @@ function SkeletonTracingReducer(state: OxalisState, action: Action): OxalisState
.getOrElse(state);
}

case "DESELECT_ACTIVE_TREE": {
return update(state, {
tracing: {
skeleton: {
activeNodeId: { $set: null },
activeTreeId: { $set: null },
},
},
});
}

case "SET_ACTIVE_GROUP": {
return update(state, {
tracing: {
Expand All @@ -365,6 +377,16 @@ function SkeletonTracingReducer(state: OxalisState, action: Action): OxalisState
});
}

case "DESELECT_ACTIVE_GROUP": {
return update(state, {
tracing: {
skeleton: {
activeGroupId: { $set: null },
},
},
});
}

case "MERGE_TREES": {
const { sourceNodeId, targetNodeId } = action;
return mergeTrees(skeletonTracing, sourceNodeId, targetNodeId, restrictions)
Expand Down Expand Up @@ -592,12 +614,15 @@ function SkeletonTracingReducer(state: OxalisState, action: Action): OxalisState
}

case "SET_TREE_GROUPS": {
const { treeGroups } = action;
const updatedTrees = removeMissingGroupsFromTrees(skeletonTracing, treeGroups);
return update(state, {
tracing: {
skeleton: {
treeGroups: {
$set: action.treeGroups,
},
trees: { $merge: updatedTrees },
},
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,19 @@ export function createTreeMapFromTreeArray(trees: Array<ServerSkeletonTracingTre
"treeId",
);
}

export function removeMissingGroupsFromTrees(
skeletonTracing: SkeletonTracing,
treeGroups: Array<TreeGroup>,
): TreeMap {
// Change the groupId of trees for groups that no longer exist
const groupIds = Array.from(mapGroups(treeGroups, group => group.groupId));
const changedTrees = {};
Object.keys(skeletonTracing.trees).forEach(treeId => {
const tree = skeletonTracing.trees[Number(treeId)];
if (tree.groupId != null && !groupIds.includes(tree.groupId)) {
changedTrees[treeId] = { ...tree, groupId: null };
}
});
return changedTrees;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @flow
import * as React from "react";
import { Modal, Button } from "antd";
import messages from "messages";

type Props = {
onJustDeleteGroup: () => void,
onDeleteGroupAndTrees: () => void,
onCancel: () => void,
};

export default function DeleteGroupModalView({
onJustDeleteGroup,
onDeleteGroupAndTrees,
onCancel,
}: Props) {
return (
<Modal
visible
title={messages["tracing.group_deletion_message"]}
onOk={onJustDeleteGroup}
onCancel={onCancel}
footer={[
<Button key="back" onClick={onCancel}>
Cancel
</Button>,
<Button key="submit-all" onClick={onDeleteGroupAndTrees}>
Remove group recursively
</Button>,
<Button key="submit-groups-only" type="primary" onClick={onJustDeleteGroup}>
Remove group only
</Button>,
]}
>
Do you really want to remove the selected group? If you want to remove the group with all its
trees and subgroups recursively, select &quot;Remove group recursively&quot;. If you want to
remove just the group and keep the subtrees and subgroups select &quot;Remove group
only&quot;.
</Modal>
);
}
Loading

0 comments on commit 5b83ca0

Please sign in to comment.