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

Enable merger mode in skeleton and hybrid tracings #3619

Merged
merged 38 commits into from
Mar 25, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8b2753c
added todo notes
MichaelBuessemeyer Nov 30, 2018
24a7444
making srcipt readable
MichaelBuessemeyer Dec 5, 2018
cf557fa
reworked script even more
MichaelBuessemeyer Jan 11, 2019
5fcb63a
integrated merger mode into the ui
MichaelBuessemeyer Jan 11, 2019
cacc165
further refactoring of the merger script: added flow type checking
MichaelBuessemeyer Jan 18, 2019
5ec844f
merged master into this branch and resolved conflicts in user_setting…
MichaelBuessemeyer Jan 18, 2019
1766303
fixed disabling merger mode
MichaelBuessemeyer Jan 18, 2019
f162409
changed pre processing of the merger modeto make it parallel for each…
MichaelBuessemeyer Jan 25, 2019
5e882e9
applied pr feedback
MichaelBuessemeyer Jan 29, 2019
79c5538
applied feedback
MichaelBuessemeyer Jan 30, 2019
8b14aa5
fixed table content to be wrapped with tbody element
MichaelBuessemeyer Feb 1, 2019
9283f63
merged origin/master into enavle-merger-mode-in-hybrid-tracings
MichaelBuessemeyer Feb 1, 2019
0cc3f25
fixed flow complaining
MichaelBuessemeyer Feb 1, 2019
f2be5db
Merge branch 'master' of github.com:scalableminds/webknossos into ena…
philippotto Feb 25, 2019
0122d3c
updated to master
MichaelBuessemeyer Feb 28, 2019
c78f8df
added rough merger mode explaination to docs
MichaelBuessemeyer Feb 28, 2019
d71f312
added changelog entry
MichaelBuessemeyer Feb 28, 2019
4a3b156
added overwrite action that also adjusts the mapping when a whole tre…
MichaelBuessemeyer Feb 28, 2019
0098ad5
Merge branch 'master' into enable-merger-mode-in-hybrid-tracings
MichaelBuessemeyer Feb 28, 2019
c83325c
updated to master and resolved conflicts
MichaelBuessemeyer Mar 11, 2019
59f0753
made code pretty
MichaelBuessemeyer Mar 11, 2019
2453cd4
Merge branch 'master' into enable-merger-mode-in-hybrid-tracings
MichaelBuessemeyer Mar 11, 2019
b100805
Merge branch 'master' of github.com:scalableminds/webknossos into ena…
MichaelBuessemeyer Mar 13, 2019
b7890c6
added isMergerModeEnabeld to temporary store configs
MichaelBuessemeyer Mar 13, 2019
a58d6f5
fixed flow errors
MichaelBuessemeyer Mar 13, 2019
2885fb7
Merge branch 'enable-merger-mode-in-hybrid-tracings' of github.com:sc…
MichaelBuessemeyer Mar 13, 2019
0eeb881
fixed spelling and node prepocessing bug of merger mode
MichaelBuessemeyer Mar 13, 2019
825d5fc
Merge branch 'master' of github.com:scalableminds/webknossos into ena…
MichaelBuessemeyer Mar 13, 2019
6e6865d
Update tracing_ui.md
MichaelBuessemeyer Mar 13, 2019
cb2b65c
Update CHANGELOG.md
MichaelBuessemeyer Mar 21, 2019
60e09c8
Merge branch 'master' into enable-merger-mode-in-hybrid-tracings
MichaelBuessemeyer Mar 21, 2019
6b8d2ba
Merge branch 'master' of github.com:scalableminds/webknossos into ena…
MichaelBuessemeyer Mar 21, 2019
7d3f8b9
disabled merger mode in settings when no uint32 segm is available
MichaelBuessemeyer Mar 21, 2019
86eeec3
disabled merger mode switch when no mappings are supported
MichaelBuessemeyer Mar 25, 2019
d6b7c5b
Merge branch 'master' into enable-merger-mode-in-hybrid-tracings
MichaelBuessemeyer Mar 25, 2019
52093f1
added comment
MichaelBuessemeyer Mar 25, 2019
c946382
Merge branch 'master' of github.com:scalableminds/webknossos into ena…
MichaelBuessemeyer Mar 25, 2019
6140705
Merge branches 'enable-merger-mode-in-hybrid-tracings' and 'enable-me…
MichaelBuessemeyer Mar 25, 2019
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
3 changes: 2 additions & 1 deletion app/assets/javascripts/oxalis/api/api_latest.js
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,7 @@ class UtilsApi {
* - SHUFFLE_ALL_TREE_COLORS
* - CREATE_COMMENT
* - DELETE_COMMENT
* @returns {function()} - A function used to unregister the overwriteFunction
*
*
* @example
Expand All @@ -926,7 +927,7 @@ class UtilsApi {
actionName: string,
overwriteFunction: (store: S, next: (action: A) => void, originalAction: A) => void,
) {
overwriteAction(actionName, overwriteFunction);
return overwriteAction(actionName, overwriteFunction);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export function overwriteAction<S, A>(
}

overwrites[actionName] = overwriteFunction;
return () => {
delete overwrites[actionName];
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
};
}

export function removeOverwrite(actionName: string) {
Expand Down
252 changes: 252 additions & 0 deletions app/assets/javascripts/oxalis/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import { Modal } from "antd";
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
import api from "oxalis/api/internal_api";

const unregisterKeyHandlers = [];
const unregisterOverwrites = [];
let isCodeActive = false;

export function enableMergerMode() {
if (isCodeActive) {
return;
}
isCodeActive = true;
const treeColors = {};
const colorMapping = {};

/* For each segment keep track of the number of
nodes that were placed within. This allows us
to change back the color of a segment if and
only if all nodes were removed from a segment. */
const nodesPerSegment = [];
philippotto marked this conversation as resolved.
Show resolved Hide resolved

function getTreeColor(treeId) {
let color = treeColors[treeId];
// generate a new color if tree was never seen before
if (color === undefined) {
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
color = Math.ceil(127 * Math.random());
treeColors[treeId] = color;
}
return color;
}

function mapSegmentColorToTree(segId, treeId) {
// add segment to color mapping
const color = getTreeColor(treeId);
colorMapping[segId] = color;
}

function deleteColorMappingOfSegment(segId) {
// remove segment from color mapping
delete colorMapping[segId];
}

/* This function is used to increment the reference count /
number of nodes mapped to the given segment */
function increaseNodesOfSegment(segementId) {
const currentValue = nodesPerSegment[segementId];
if (currentValue === undefined) {
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
nodesPerSegment[segementId] = 1;
} else {
nodesPerSegment[segementId] = currentValue + 1;
}
return nodesPerSegment[segementId];
}

/* This function is used to decrement the reference count /
number of nodes mapped to the given segment */
function decreaseNodesOfSegment(segementId) {
const currentValue = nodesPerSegment[segementId];
nodesPerSegment[segementId] = currentValue - 1;
return nodesPerSegment[segementId];
}

function getAllNodesWithTreeId() {
const trees = api.tracing.getAllTrees();
const nodes = [];
// consider using lodash to create a deep copy and than modify
// this copy by adding the tree id to each node => alrighty
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
Object.keys(trees).forEach(treeId => {
const currentTree = trees[treeId];
for (const node of currentTree.nodes.values()) {
const nodeWithTreeId = Object.assign({}, node, { treeId });
nodes.push(nodeWithTreeId);
}
});
return nodes;
}

const nodes = getAllNodesWithTreeId();
const segementationLayerName = api.data.getVolumeTracingLayerName();

const nodeSegmentMap = {};

const segmentationOpacity = api.data.getConfiguration("segmentationOpacity");
let segmentationOn = true;

/* Here we intercept calls to the "addNode" method. This allows us to look up the segment id at the specified
point and display it in the same color as the rest of the aggregate. */

async function createNodeOverwrite(store, call, action) {
call(action);

const pos = action.position;
const segmentId = await api.data.getDataValue(segementationLayerName, pos);

const activeTreeId = api.tracing.getActiveTreeId();
const activeNodeId = api.tracing.getActiveNodeId();

// If there is no segment id, the node was set to close to a border between segments
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
if (!segmentId) {
Modal.info({ title: "You've set a point too close to grey. The node will be removed now." });
api.tracing.deleteNode(activeNodeId, activeTreeId);
return;
}

// set segment id
nodeSegmentMap[activeNodeId] = segmentId;

// count references
increaseNodesOfSegment(segmentId);
mapSegmentColorToTree(segmentId, activeTreeId);

// update mapping
api.data.setMapping(segementationLayerName, colorMapping);
}
unregisterOverwrites.push(api.utils.registerOverwrite("CREATE_NODE", createNodeOverwrite));

/* Overwrite the "deleteActiveNode" method in such a way
that a segment changes back its color as soon as all
nodes are deleted from it.
=> also do this on tree delete if possible (later) */
function deleteActiveNodeOverwrite(store, call, action) {
const activeNodeId = api.tracing.getActiveNodeId();
if (activeNodeId == null) {
return;
}

const segmentId = nodeSegmentMap[activeNodeId];
const numberOfNodesMappedToSegment = decreaseNodesOfSegment(segmentId);

if (numberOfNodesMappedToSegment === 0) {
// Reset color of all segments that were mapped to this tree
deleteColorMappingOfSegment(segmentId);
api.data.setMapping(segementationLayerName, colorMapping);
}
call(action);
}
unregisterOverwrites.push(api.utils.registerOverwrite("DELETE_NODE", deleteActiveNodeOverwrite));

// changes the opacity of the segmentation layer
function changeOpacity() {
if (segmentationOn) {
api.data.setConfiguration("segmentationOpacity", 0);
segmentationOn = false;
} else {
api.data.setConfiguration("segmentationOpacity", segmentationOpacity);
segmentationOn = true;
}
}

function shuffleColorOfCurrentTree() {
const setNewColorOfCurrentActiveTree = () => {
const activeTreeId = api.tracing.getActiveTreeId();
const oldColor = getTreeColor(activeTreeId);
// reset color of active tree
treeColors[activeTreeId] = undefined;
// applied the change of color to all segments with the same color
Object.keys(colorMapping).forEach(key => {
if (colorMapping[key] === oldColor) {
colorMapping[key] = getTreeColor(activeTreeId);
}
});
// update segmentation
api.data.setMapping(segementationLayerName, colorMapping);
};

Modal.confirm({
title: "Do you want to set a new Color?",
onOk: setNewColorOfCurrentActiveTree,
onCancel() {},
});
}

async function mergeSegmentsOfAlreadyExistingTrees(index = 0) {
const numbOfNodes = nodes.length;
if (index >= numbOfNodes) {
return;
}

if (index % 50 === 0) {
// TODO: Make visible to user
console.log(`Processing node ${index} of ${numbOfNodes}`);
}

const node = nodes[index];
const pos = node.position;
const treeId = node.treeId;

const [segMinVec, segMaxVec] = api.data.getBoundingBox(segementationLayerName);

// skip nodes outside segmentation
if (
pos[0] < segMinVec[0] ||
pos[1] < segMinVec[1] ||
pos[2] < segMinVec[2] ||
pos[0] >= segMaxVec[0] ||
pos[1] >= segMaxVec[1] ||
pos[2] >= segMaxVec[2]
) {
mergeSegmentsOfAlreadyExistingTrees(index + 1);
return;
}

// TODO: Make visible to user
// why here a + 1?
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
console.log(`Retrying node ${index + 1} of ${numbOfNodes}`);

const segmentId = await api.data.getDataValue(segementationLayerName, pos);
// this should never happen
if (segmentId === null) {
return;
}

if (segmentId > 0) {
// store segment id
nodeSegmentMap[node.id] = segmentId;

// add to agglomerate
increaseNodesOfSegment(segmentId);
mapSegmentColorToTree(segmentId, treeId);
console.log("set", segmentId, treeId);
}

if (index < numbOfNodes - 1) {
// continue with next node if needed
mergeSegmentsOfAlreadyExistingTrees(index + 1);
} else {
api.data.setMapping("segmentation", colorMapping);
}
}

unregisterKeyHandlers.push(
api.utils.registerKeyHandler("9", () => {
changeOpacity();
}),
);
unregisterKeyHandlers.push(
api.utils.registerKeyHandler("8", () => {
shuffleColorOfCurrentTree();
}),
);
// maybe first ask the user via modal
mergeSegmentsOfAlreadyExistingTrees();
}

export function disableMergerMode() {
if (!isCodeActive) {
return;
}
isCodeActive = false;
unregisterOverwrites.forEach(unregisterFunction => unregisterFunction());
unregisterKeyHandlers.forEach(unregisterObject => unregisterObject.unregister());
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import Constants, {
type Vector6,
} from "oxalis/constants";
import * as Utils from "libs/utils";
import { enableMergerMode, disableMergerMode } from "oxalis/script";

const Panel = Collapse.Panel;

Expand All @@ -65,8 +66,13 @@ type UserSettingsViewProps = {
brushSize: number,
};

class UserSettingsView extends PureComponent<UserSettingsViewProps> {
type State = {
isMergerModeEnabled: boolean,
};

class UserSettingsView extends PureComponent<UserSettingsViewProps, State> {
onChangeUser: { [$Keys<UserConfiguration>]: Function };
state = { isMergerModeEnabled: false };

componentWillMount() {
// cache onChange handler
Expand All @@ -75,6 +81,14 @@ class UserSettingsView extends PureComponent<UserSettingsViewProps> {
);
}

handleMergerModeChange = value => {
if (value) {
enableMergerMode();
} else {
disableMergerMode();
}
};

getViewportOptions = () => {
switch (this.props.viewMode) {
case Constants.MODE_PLANE_TRACING:
Expand Down Expand Up @@ -255,6 +269,14 @@ class UserSettingsView extends PureComponent<UserSettingsViewProps> {
value={this.props.userConfiguration.highlightCommentedNodes}
onChange={this.onChangeUser.highlightCommentedNodes}
/>
<SwitchSetting
label="Enable Merger Mode"
value={this.state.isMergerModeEnabled}
onChange={value => {
this.setState({ isMergerModeEnabled: value });
this.handleMergerModeChange(value);
}}
/>
</Panel>,
);
}
Expand Down