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

Support transforming the skeleton layer with affine and TSP transforms #7588

Merged
merged 46 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
9f2aae2
transform nodes in shader according to skeleton transforms
philippotto Jan 23, 2024
2027ccb
disable frustum culling; make center-node respect transforms; also st…
philippotto Jan 23, 2024
315fe99
also adapt edge shader
philippotto Jan 23, 2024
e948a08
adapt todo comment
philippotto Jan 23, 2024
1c48d4d
adapt all usages of node.position to either use untransformedPosition…
philippotto Jan 24, 2024
d2bc803
fix position when creating new nodes
philippotto Jan 24, 2024
defc465
also show transforms icon for skeleton layer in side bar
philippotto Jan 24, 2024
580e817
use current position when exporting skeleton as NML
philippotto Jan 24, 2024
2c05beb
support TSPs in node shader
philippotto Jan 25, 2024
22161c0
support TSPs in edge shader
philippotto Jan 25, 2024
8f6d15b
fix linting
philippotto Jan 25, 2024
ba2e5c0
fix cyclic dependency
philippotto Jan 25, 2024
18efa67
fix node picking for transformed nodes
philippotto Jan 25, 2024
4bb948e
fix front-end unit tests
philippotto Jan 25, 2024
ad21ee1
fix nd rendering
philippotto Jan 25, 2024
31dc4b7
clear up culling todo
philippotto Jan 25, 2024
f133f29
adapt some comments
philippotto Jan 25, 2024
cb0e37d
support transformed skeleton and volume layers in merger mode
philippotto Jan 25, 2024
c338ff3
fix linting
philippotto Jan 25, 2024
2b3f475
remove has_transform uniform logic because a matrix multiplication is…
philippotto Jan 25, 2024
7c8fe27
fix node rendering in nd datasets
philippotto Jan 25, 2024
9aaae8d
fix incorrect culling setting
philippotto Jan 25, 2024
9d3256a
also do proper segment look up in createNode override
philippotto Jan 25, 2024
681225f
Merge branch 'master' into transform-skeletons
philippotto Feb 2, 2024
0ddb7ad
Merge branch 'transform-skeletons' of github.com:scalableminds/webkno…
philippotto Feb 2, 2024
1a78d15
Merge branch 'master' of github.com:scalableminds/webknossos into tra…
philippotto Feb 12, 2024
00d1466
disable skeleton tool if skeleton layer is transformed; refactor disa…
philippotto Feb 13, 2024
68164a0
provide nml download with and without transforms; encode affine and t…
philippotto Feb 13, 2024
891174c
update changelog
philippotto Feb 13, 2024
5b7064c
fix linting
philippotto Feb 13, 2024
e636e09
extract common code in merger mode
philippotto Feb 21, 2024
913cc52
misc feedback
philippotto Feb 21, 2024
9fea7b3
fix alignment of icon; assert that skeleton layer is not transformed …
philippotto Feb 21, 2024
d2c2e5d
Merge branch 'master' of github.com:scalableminds/webknossos into tra…
philippotto Mar 5, 2024
eb40d0d
fix merger mode
philippotto Mar 6, 2024
a8c0b8a
serialize positionsAreTransformed=true into transform tag in NML
philippotto Mar 6, 2024
f470b74
remove shortcut hint for '8' in merger mode modal since this hasn't b…
philippotto Mar 6, 2024
dc0adfe
also serialize positionsAreTransformed for tps transforms
philippotto Mar 6, 2024
e221889
Merge branch 'master' of github.com:scalableminds/webknossos into tra…
philippotto Mar 6, 2024
dfb80cf
fix merge errors
philippotto Mar 6, 2024
e0f2c9c
improve middleware types a bit
philippotto Mar 6, 2024
678e70e
format
philippotto Mar 6, 2024
c407a06
Merge branch 'master' of github.com:scalableminds/webknossos into tra…
philippotto Mar 11, 2024
7bd5193
don't allow node creation and moving in reducer when skeleton layer i…
philippotto Mar 12, 2024
0c31070
Merge branch 'master' into transform-skeletons
daniel-wer Mar 18, 2024
c26d984
Merge branch 'master' into transform-skeletons
philippotto Mar 18, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Segment statistics are now available for ND datasets. [#7411](https://github.com/scalableminds/webknossos/pull/7411)
- Added support for uploading N5 and Neuroglancer Precomputed datasets. [#7578](https://github.com/scalableminds/webknossos/pull/7578)
- Webknossos can now open ND Zarr datasets with arbitrary axis orders (not limited to `**xyz` anymore). [#7592](https://github.com/scalableminds/webknossos/pull/7592)
- Added support for skeleton annotations within datasets that have transformed layers. The skeleton nodes will move according to the transforms when rendering a specific layer natively. Also, downloading visible trees can be done by incorporating the current transforms. However, note that the back-end export does not take transforms into account. [#7588](https://github.com/scalableminds/webknossos/pull/7588)
- Added a new "Split from all neighboring segments" feature for the proofreading mode. [#7611](https://github.com/scalableminds/webknossos/pull/7611)
- If storage scan is enabled, the measured used storage is now displayed in the dashboard’s dataset detail view. [#7677](https://github.com/scalableminds/webknossos/pull/7677)
- Prepared support to download full stl meshes via the HTTP api. [#7587](https://github.com/scalableminds/webknossos/pull/7587)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ async function parseNmlFiles(fileList: FileList): Promise<Partial<WizardContext>
);
}
if (node1 != null && node2 != null) {
sourcePoints.push(node1.position);
targetPoints.push(node2.position);
sourcePoints.push(node1.untransformedPosition);
targetPoints.push(node2.untransformedPosition);
}
}
}
Expand Down
32 changes: 18 additions & 14 deletions frontend/javascripts/oxalis/api/api_latest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
getFlatTreeGroups,
getTreeGroupsMap,
mapGroups,
getNodePosition,
} from "oxalis/model/accessors/skeletontracing_accessor";
import {
getActiveCellId,
Expand Down Expand Up @@ -159,6 +160,7 @@ import type {
OxalisState,
SegmentGroup,
Segment,
MutableNode,
} from "oxalis/store";
import Store from "oxalis/store";
import type { ToastStyle } from "libs/toast";
Expand Down Expand Up @@ -986,9 +988,9 @@ class TracingApi {
*/
centerNode = (nodeId?: number): void => {
const skeletonTracing = assertSkeleton(Store.getState().tracing);
getNodeAndTree(skeletonTracing, nodeId).map(([, node]) =>
Store.dispatch(setPositionAction(node.position)),
);
getNodeAndTree(skeletonTracing, nodeId).map(([, node]) => {
return Store.dispatch(setPositionAction(getNodePosition(node, Store.getState())));
});
};

/**
Expand Down Expand Up @@ -1032,23 +1034,26 @@ class TracingApi {
* Measures the length of the given tree and returns the length in nanometer and in voxels.
*/
measureTreeLength(treeId: number): [number, number] {
const skeletonTracing = assertSkeleton(Store.getState().tracing);
const state = Store.getState();
const skeletonTracing = assertSkeleton(state.tracing);
const tree = skeletonTracing.trees[treeId];

if (!tree) {
throw new Error(`Tree with id ${treeId} not found.`);
}

const datasetScale = Store.getState().dataset.dataSource.scale;
const datasetScale = state.dataset.dataSource.scale;
// Pre-allocate vectors
let lengthNmAcc = 0;
let lengthVxAcc = 0;

const getPos = (node: Readonly<MutableNode>) => getNodePosition(node, state);

for (const edge of tree.edges.all()) {
const sourceNode = tree.nodes.get(edge.source);
const targetNode = tree.nodes.get(edge.target);
lengthNmAcc += V3.scaledDist(sourceNode.position, targetNode.position, datasetScale);
lengthVxAcc += V3.length(V3.sub(sourceNode.position, targetNode.position));
lengthNmAcc += V3.scaledDist(getPos(sourceNode), getPos(targetNode), datasetScale);
lengthVxAcc += V3.length(V3.sub(getPos(sourceNode), getPos(targetNode)));
}

return [lengthNmAcc, lengthVxAcc];
Expand Down Expand Up @@ -1125,14 +1130,17 @@ class TracingApi {
});
priorityQueue.queue([sourceNodeId, 0]);

const state = Store.getState();
const getPos = (node: Readonly<MutableNode>) => getNodePosition(node, state);

while (priorityQueue.length > 0) {
const [nextNodeId, distance] = priorityQueue.dequeue();
const nextNodePosition = sourceTree.nodes.get(nextNodeId).position;
const nextNodePosition = getPos(sourceTree.nodes.get(nextNodeId));

// Calculate the distance to all neighbours and update the distances.
for (const { source, target } of sourceTree.edges.getEdgesForNode(nextNodeId)) {
const neighbourNodeId = source === nextNodeId ? target : source;
const neighbourPosition = sourceTree.nodes.get(neighbourNodeId).position;
const neighbourPosition = getPos(sourceTree.nodes.get(neighbourNodeId));
const neighbourDistance =
distance + V3.scaledDist(nextNodePosition, neighbourPosition, datasetScale);

Expand Down Expand Up @@ -2605,11 +2613,7 @@ class UtilsApi {
*/
registerOverwrite<S, A>(
actionName: string,
overwriteFunction: (
store: S,
next: (action: A) => void,
originalAction: A,
) => void | Promise<void>,
overwriteFunction: (store: S, next: (action: A) => void, originalAction: A) => A | Promise<A>,
) {
return overwriteAction(actionName, overwriteFunction);
}
Expand Down
8 changes: 5 additions & 3 deletions frontend/javascripts/oxalis/api/api_v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getActiveNode,
getActiveTree,
getTree,
getNodePosition,
} from "oxalis/model/accessors/skeletontracing_accessor";
import { setActiveCellAction } from "oxalis/model/actions/volumetracing_actions";
import { getActiveCellId } from "oxalis/model/accessors/volumetracing_accessor";
Expand Down Expand Up @@ -346,9 +347,10 @@ class TracingApi {
* api.tracing.centerNode()
*/
centerNode = (nodeId?: number): void => {
const skeletonTracing = assertSkeleton(Store.getState().tracing);
const state = Store.getState();
const skeletonTracing = assertSkeleton(state.tracing);
getNodeAndTree(skeletonTracing, nodeId).map(([, node]) =>
Store.dispatch(setPositionAction(node.position)),
Store.dispatch(setPositionAction(getNodePosition(node, state))),
);
};

Expand Down Expand Up @@ -841,7 +843,7 @@ class UtilsApi {
*/
registerOverwrite<S, A>(
actionName: string,
overwriteFunction: (store: S, next: (action: A) => void, originalAction: A) => void,
overwriteFunction: (store: S, next: (action: A) => void, originalAction: A) => A | Promise<A>,
) {
overwriteAction(actionName, overwriteFunction);
}
Expand Down
6 changes: 5 additions & 1 deletion frontend/javascripts/oxalis/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,5 +376,9 @@ export enum BLEND_MODES {
}

export const Identity4x4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
export const IdentityTransform = { type: "affine", affineMatrix: Identity4x4 } as const;
export const IdentityTransform = {
type: "affine",
affineMatrix: Identity4x4,
affineMatrixInv: Identity4x4,
} as const;
export const EMPTY_OBJECT = {} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
getActiveNode,
getNodeAndTree,
getNodeAndTreeOrNull,
getNodePosition,
untransformNodePosition,
} from "oxalis/model/accessors/skeletontracing_accessor";
import {
getInputCatcherRect,
Expand Down Expand Up @@ -197,10 +199,10 @@ export function moveNode(
op(vector[1] * zoomFactor * scaleFactor[1]),
op(vector[2] * zoomFactor * scaleFactor[2]),
];
const [x, y, z] = activeNode.position;
const [x, y, z] = getNodePosition(activeNode, state);
Store.dispatch(
setNodePositionAction(
[x + delta[0], y + delta[1], z + delta[2]],
untransformNodePosition([x + delta[0], y + delta[1], z + delta[2]], state),
philippotto marked this conversation as resolved.
Show resolved Hide resolved
activeNode.id,
activeTree.treeId,
),
Expand All @@ -213,7 +215,11 @@ export function finishNodeMovement(nodeId: number) {
getSkeletonTracing(Store.getState().tracing).map((skeletonTracing) =>
getNodeAndTree(skeletonTracing, nodeId).map(([activeTree, node]) => {
Store.dispatch(
setNodePositionAction(V3.round(node.position, [0, 0, 0]), node.id, activeTree.treeId),
setNodePositionAction(
V3.round(node.untransformedPosition, [0, 0, 0]),
node.id,
activeTree.treeId,
),
);
}),
);
Expand All @@ -228,15 +234,16 @@ export function setWaypoint(
const activeNodeMaybe = getActiveNode(skeletonTracing);
const rotation = getRotationOrtho(activeViewport);
// set the new trace direction
activeNodeMaybe.map((activeNode) =>
Store.dispatch(
activeNodeMaybe.map((activeNode) => {
const activeNodePosition = getNodePosition(activeNode, Store.getState());
return Store.dispatch(
setDirectionAction([
position[0] - activeNode.position[0],
position[1] - activeNode.position[1],
position[2] - activeNode.position[2],
position[0] - activeNodePosition[0],
position[1] - activeNodePosition[1],
position[2] - activeNodePosition[2],
]),
),
);
);
});
const state = Store.getState();
// Create a new tree automatically if the corresponding setting is true and allowed
const createNewTree =
Expand Down Expand Up @@ -276,7 +283,7 @@ function addNode(

Store.dispatch(
createNodeAction(
position,
untransformNodePosition(position, state),
state.flycam.additionalCoordinates,
rotation,
OrthoViewToNumber[Store.getState().viewModeData.plane.activeViewport],
Expand All @@ -293,12 +300,10 @@ function addNode(
if (center) {
// we created a new node, so get a new reference from the current store state
const newState = Store.getState();
enforce(getActiveNode)(newState.tracing.skeleton).map(
(
newActiveNode, // Center the position of the active node without modifying the "third" dimension (see centerPositionAnimated)
) =>
// This is important because otherwise the user cannot continue to trace until the animation is over
api.tracing.centerPositionAnimated(newActiveNode.position, true),
enforce(getActiveNode)(newState.tracing.skeleton).map((newActiveNode) =>
// Center the position of the active node without modifying the "third" dimension (see centerPositionAnimated)
// This is important because otherwise the user cannot continue to trace until the animation is over
api.tracing.centerPositionAnimated(getNodePosition(newActiveNode, state), true),
);
}

Expand Down
4 changes: 2 additions & 2 deletions frontend/javascripts/oxalis/controller/td_controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
moveTDViewYAction,
moveTDViewByVectorWithoutTimeTrackingAction,
} from "oxalis/model/actions/view_mode_actions";
import { getActiveNode } from "oxalis/model/accessors/skeletontracing_accessor";
import { getActiveNode, getNodePosition } from "oxalis/model/accessors/skeletontracing_accessor";
import { voxelToNm } from "oxalis/model/scaleinfo";
import CameraController from "oxalis/controller/camera_controller";
import PlaneView from "oxalis/view/plane_view";
Expand Down Expand Up @@ -128,7 +128,7 @@ class TDController extends React.PureComponent<Props> {
// This happens because the selection of the node does not trigger a call to setTargetAndFixPosition directly.
// Thus we do it manually whenever the active node changes.
getActiveNode(this.props.tracing.skeleton).map((activeNode) =>
this.setTargetAndFixPosition(activeNode.position),
this.setTargetAndFixPosition(getNodePosition(activeNode, Store.getState())),
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type { ModifierKeys } from "libs/input";
import { InputKeyboard, InputKeyboardNoLoop, InputMouse } from "libs/input";
import type { Matrix4x4 } from "libs/mjs";
import { V3 } from "libs/mjs";
import { getActiveNode, getMaxNodeId } from "oxalis/model/accessors/skeletontracing_accessor";
import {
getActiveNode,
getMaxNodeId,
getNodePosition,
untransformNodePosition,
} from "oxalis/model/accessors/skeletontracing_accessor";
import { getRotation, getPosition, getMoveOffset3d } from "oxalis/model/accessors/flycam_accessor";
import { getViewportScale } from "oxalis/model/accessors/view_mode_accessor";
import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers";
Expand Down Expand Up @@ -198,14 +203,19 @@ class ArbitraryController extends React.PureComponent<Props> {
},
// Recenter active node
s: () => {
const skeletonTracing = Store.getState().tracing.skeleton;
const state = Store.getState();
const skeletonTracing = state.tracing.skeleton;

if (!skeletonTracing) {
return;
}

getActiveNode(skeletonTracing).map((activeNode) =>
api.tracing.centerPositionAnimated(activeNode.position, false, activeNode.rotation),
api.tracing.centerPositionAnimated(
getNodePosition(activeNode, state),
false,
activeNode.rotation,
),
);
},
".": () => this.nextNode(true),
Expand Down Expand Up @@ -357,12 +367,18 @@ class ArbitraryController extends React.PureComponent<Props> {
if (!Store.getState().temporaryConfiguration.flightmodeRecording) {
return;
}

const position = getPosition(Store.getState().flycam);
const rotation = getRotation(Store.getState().flycam);
const additionalCoordinates = Store.getState().flycam.additionalCoordinates;
const state = Store.getState();
const position = getPosition(state.flycam);
const rotation = getRotation(state.flycam);
const additionalCoordinates = state.flycam.additionalCoordinates;
Store.dispatch(
createNodeAction(position, additionalCoordinates, rotation, constants.ARBITRARY_VIEW, 0),
createNodeAction(
untransformNodePosition(position, state),
additionalCoordinates,
rotation,
constants.ARBITRARY_VIEW,
0,
),
);
}

Expand Down
Loading