Skip to content

Commit 537e3fc

Browse files
Fix loading ad-hoc meshes for transformed layers (#8992)
### URL of deployed dev instance (used for testing): - https://fixadhocmeshesfortransformedlayers.webknossos.xyz/ ### Steps to test: - Test this with multiple datasets: one where all layers are transformed, and one where not all layers are transformed - make sure the datasets have a segmentation layer - register segments, load ad hoc meshes, and load precomputed meshes: - register some segments, scroll within the viewport, and then click on some segments in the segment list on the right. the viewport sshould jump to the right segment. do this with and without rendered transformations, and also toggle transformation rendering after registering the segments in the list - load some ad hoc meshes by selecting this option... - a) from the context menu in the segment list - b) from the context menu in the viewport - same for precomputed meshes -> again, this should be working with and without rendered transformations ### To do - [x] I think I found another bug (reproduced on wk.org): click segment with transformed layers, scroll away, turn rendering with transformations off, click segment in segment list -> wrong place is focused - [x] testing!!! ### Issues: - fixes #8913 ------ (Please delete unneeded items, merge only when none are left open) - [x] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`) - [ ] Added migration guide entry if applicable (edit the same file as for the changelog) - [ ] Updated [documentation](../blob/master/docs) if applicable - [ ] Adapted [wk-libs python client](https://github.com/scalableminds/webknossos-libs/tree/master/webknossos/webknossos/client) if relevant API parts change - [ ] Removed dev-only changes like prints and application.conf edits - [x] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [ ] Needs datastore update after deployment
1 parent a71f7d0 commit 537e3fc

File tree

6 files changed

+73
-10
lines changed

6 files changed

+73
-10
lines changed

frontend/javascripts/viewer/controller/combinations/segmentation_handlers.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {
44
getSegmentIdForPosition,
55
getSegmentIdForPositionAsync,
66
} from "viewer/controller/combinations/volume_handlers";
7-
import { getMappingInfo } from "viewer/model/accessors/dataset_accessor";
7+
import {
8+
getMappingInfo,
9+
getVisibleSegmentationLayer,
10+
} from "viewer/model/accessors/dataset_accessor";
11+
import { globalToLayerTransformedPosition } from "viewer/model/accessors/dataset_layer_transformation_accessor";
812
import { getTreeNameForAgglomerateSkeleton } from "viewer/model/accessors/skeletontracing_accessor";
913
import { calculateGlobalPos } from "viewer/model/accessors/view_mode_accessor";
1014
import {
@@ -79,9 +83,22 @@ export function handleClickSegment(clickPosition: Point2) {
7983
const state = Store.getState();
8084
const globalPosition = calculateGlobalPos(state, clickPosition);
8185
const segmentId = getSegmentIdForPosition(globalPosition.rounded);
86+
const visibleSegmentationLayer = getVisibleSegmentationLayer(state);
87+
const positionInSegmentationLayerSpace =
88+
visibleSegmentationLayer != null
89+
? (globalToLayerTransformedPosition(
90+
globalPosition.rounded,
91+
visibleSegmentationLayer.name,
92+
"segmentation",
93+
state,
94+
).map(Math.floor) as Vector3)
95+
: null;
96+
8297
const { additionalCoordinates } = state.flycam;
8398

84-
if (segmentId > 0) {
85-
Store.dispatch(clickSegmentAction(segmentId, globalPosition.rounded, additionalCoordinates));
99+
if (segmentId > 0 && positionInSegmentationLayerSpace != null) {
100+
Store.dispatch(
101+
clickSegmentAction(segmentId, positionInSegmentationLayerSpace, additionalCoordinates),
102+
);
86103
}
87104
}

frontend/javascripts/viewer/controller/combinations/skeleton_handlers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,10 @@ export function handleOpenContextMenu(
136136
meshIntersectionPosition?: Vector3 | null | undefined,
137137
unmappedSegmentId?: number | null | undefined,
138138
) {
139-
const { activeViewport } = Store.getState().viewModeData.plane;
139+
const state = Store.getState();
140+
const { activeViewport } = state.viewModeData.plane;
140141

141142
const nodeId = maybeGetNodeIdFromPosition(planeView, position, plane, isTouch);
142-
const state = Store.getState();
143143
// Use calculateMaybeGlobalPos instead of calculateGlobalPos, since calculateGlobalPos
144144
// only works for the data viewports, but this function is also called for the 3d viewport.
145145
const globalPosition = calculateMaybeGlobalPos(state, position);

frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,24 @@ export function globalToLayerTransformedPosition(
504504
}
505505
return globalPos;
506506
}
507+
508+
export function layerToGlobalTransformedPosition(
509+
layerPos: Vector3,
510+
layerName: string,
511+
layerCategory: APIDataLayer["category"] | "skeleton",
512+
state: WebknossosState,
513+
): Vector3 {
514+
const layerDescriptor =
515+
layerCategory !== "skeleton"
516+
? getLayerByName(state.dataset, layerName, true)
517+
: ({ name: "skeleton", category: "skeleton" } as APISkeletonLayer);
518+
const layerTransforms = getTransformsForLayerOrNull(
519+
state.dataset,
520+
layerDescriptor,
521+
state.datasetConfiguration.nativelyRenderedLayerName,
522+
);
523+
if (layerTransforms) {
524+
return transformPointUnscaled(layerTransforms)(layerPos);
525+
}
526+
return layerPos;
527+
}

frontend/javascripts/viewer/view/context_menu.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ import type {
154154
VolumeTracing,
155155
} from "viewer/store";
156156

157+
import { globalToLayerTransformedPosition } from "viewer/model/accessors/dataset_layer_transformation_accessor";
157158
import { deleteNodeAsUserAction } from "viewer/model/actions/skeletontracing_actions_with_effects";
158159
import { type MutableNode, type Tree, TreeMap } from "viewer/model/types/tree_types";
159160
import Store from "viewer/store";
@@ -1055,9 +1056,24 @@ function getNoNodeContextMenuOptions(props: NoNodeContextMenuProps): ItemType[]
10551056
const segmentOrSuperVoxel =
10561057
isProofreadingActive && maybeUnmappedSegmentId != null ? "Supervoxel" : "Segment";
10571058
Store.dispatch(maybeFetchMeshFilesAction(visibleSegmentationLayer, dataset, false));
1059+
const positionInLayerSpace =
1060+
globalPosition != null && visibleSegmentationLayer != null
1061+
? globalToLayerTransformedPosition(
1062+
globalPosition,
1063+
visibleSegmentationLayer.name,
1064+
"segmentation",
1065+
Store.getState(),
1066+
)
1067+
: null;
10581068

10591069
const loadPrecomputedMesh = async () => {
1060-
if (!currentMeshFile || !visibleSegmentationLayer || globalPosition == null) return;
1070+
if (
1071+
!currentMeshFile ||
1072+
!visibleSegmentationLayer ||
1073+
globalPosition == null ||
1074+
positionInLayerSpace == null
1075+
)
1076+
return;
10611077
// Ensure that the segment ID is loaded, since a mapping might have been activated
10621078
// shortly before
10631079
const segmentId = await getSegmentIdForPositionAsync(globalPosition);
@@ -1070,7 +1086,7 @@ function getNoNodeContextMenuOptions(props: NoNodeContextMenuProps): ItemType[]
10701086
Store.dispatch(
10711087
loadPrecomputedMeshAction(
10721088
segmentId,
1073-
globalPosition,
1089+
positionInLayerSpace,
10741090
additionalCoordinates,
10751091
currentMeshFile.name,
10761092
undefined,
@@ -1156,7 +1172,7 @@ function getNoNodeContextMenuOptions(props: NoNodeContextMenuProps): ItemType[]
11561172
};
11571173

11581174
const computeMeshAdHoc = () => {
1159-
if (!visibleSegmentationLayer || globalPosition == null) {
1175+
if (!visibleSegmentationLayer || globalPosition == null || positionInLayerSpace == null) {
11601176
return;
11611177
}
11621178

@@ -1167,7 +1183,7 @@ function getNoNodeContextMenuOptions(props: NoNodeContextMenuProps): ItemType[]
11671183
return;
11681184
}
11691185

1170-
Store.dispatch(loadAdHocMeshAction(segmentId, globalPosition, additionalCoordinates));
1186+
Store.dispatch(loadAdHocMeshAction(segmentId, positionInLayerSpace, additionalCoordinates));
11711187
};
11721188

11731189
const showAutomatedSegmentationServicesModal = (errorMessage: string, entity: string) =>

frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
getMaybeSegmentIndexAvailability,
5959
getVisibleSegmentationLayer,
6060
} from "viewer/model/accessors/dataset_accessor";
61+
import { layerToGlobalTransformedPosition } from "viewer/model/accessors/dataset_layer_transformation_accessor";
6162
import { getAdditionalCoordinatesAsString } from "viewer/model/accessors/flycam_accessor";
6263
import { AnnotationTool } from "viewer/model/accessors/tool_accessor";
6364
import {
@@ -807,7 +808,13 @@ class SegmentsView extends React.Component<Props, State> {
807808
);
808809
return;
809810
}
810-
this.props.setPosition(segment.somePosition);
811+
const transformedPosition = layerToGlobalTransformedPosition(
812+
segment.somePosition,
813+
visibleSegmentationLayer.name,
814+
"segmentation",
815+
Store.getState(),
816+
);
817+
this.props.setPosition(transformedPosition);
811818
const segmentAdditionalCoordinates = segment.someAdditionalCoordinates;
812819
if (segmentAdditionalCoordinates != null) {
813820
this.props.setAdditionalCoordinates(segmentAdditionalCoordinates);

unreleased_changes/8992.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Fixed
2+
- Fixed ad-hoc mesh loading and jumping to registered segments for transformed datasets.

0 commit comments

Comments
 (0)