Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 18 additions & 14 deletions src/layer/segmentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,9 +796,6 @@ export class SegmentationUserLayer extends Base {
readonly spatialSkeletonState = this.registerDisposer(
new SpatialSkeletonState(),
);
readonly selectedSpatialSkeletonNodeId = new WatchableValue<
number | undefined
>(undefined);
readonly selectedSpatialSkeletonNodeInfo = new WatchableValue<
SelectedSpatialSkeletonNodeInfo | undefined
>(undefined);
Expand Down Expand Up @@ -994,7 +991,7 @@ export class SegmentationUserLayer extends Base {
};

ensureSpatialSkeletonInspectionFromSelection = () => {
const selectedNodeId = this.selectedSpatialSkeletonNodeId.value;
const selectedNodeId = this.selectedSpatialSkeletonNodeInfo.value?.nodeId;
const selectedNode =
selectedNodeId === undefined
? undefined
Expand Down Expand Up @@ -1096,16 +1093,23 @@ export class SegmentationUserLayer extends Base {
const nextSelectedSegmentId = getSegmentIdFromLayerSelectionValue(
nextLayerSelectionState,
);
if (this.selectedSpatialSkeletonNodeId.value !== nextSelectedNodeId) {
this.selectedSpatialSkeletonNodeId.value = nextSelectedNodeId;
}
const selectedNodeInfo = this.selectedSpatialSkeletonNodeInfo.value;
if (
selectedNodeInfo !== undefined &&
(selectedNodeInfo.nodeId !== nextSelectedNodeId ||
selectedNodeInfo.segmentId !== nextSelectedSegmentId)
if (nextSelectedNodeId === undefined) {
if (selectedNodeInfo !== undefined) {
this.selectedSpatialSkeletonNodeInfo.value = undefined;
}
} else if (
selectedNodeInfo?.nodeId !== nextSelectedNodeId ||
selectedNodeInfo?.segmentId !== nextSelectedSegmentId
) {
this.selectedSpatialSkeletonNodeInfo.value = undefined;
// Preserve rich info (position, sourceState) when only the segment ID
// changed for the same node; otherwise replace with minimal state so
// the render layer always has a valid nodeId+segmentId even after
// history navigation where we have no position or sourceState.
this.selectedSpatialSkeletonNodeInfo.value =
selectedNodeInfo?.nodeId === nextSelectedNodeId
? { ...selectedNodeInfo, segmentId: nextSelectedSegmentId }
: { nodeId: nextSelectedNodeId, segmentId: nextSelectedSegmentId };
}
};
this.registerDisposer(
Expand Down Expand Up @@ -1598,7 +1602,7 @@ export class SegmentationUserLayer extends Base {
displayState,
{
sources2d: slicePanelSources,
selectedNodeId: this.selectedSpatialSkeletonNodeId,
selectedNodeInfo: this.selectedSpatialSkeletonNodeInfo,
pendingNodePositionVersion:
this.spatialSkeletonState.pendingNodePositionVersion,
getPendingNodePosition: (nodeId) =>
Expand Down Expand Up @@ -1631,7 +1635,7 @@ export class SegmentationUserLayer extends Base {
mesh,
displayState,
{
selectedNodeId: this.selectedSpatialSkeletonNodeId,
selectedNodeInfo: this.selectedSpatialSkeletonNodeInfo,
pendingNodePositionVersion:
this.spatialSkeletonState.pendingNodePositionVersion,
getPendingNodePosition: (nodeId) =>
Expand Down
36 changes: 20 additions & 16 deletions src/skeleton/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1817,9 +1817,14 @@ export abstract class MultiscaleSpatiallyIndexedSkeletonSource extends Multiscal
type SpatiallyIndexedSkeletonSourceEntry =
SliceViewSingleResolutionSource<SpatiallyIndexedSkeletonSource>;

interface SelectedSkeletonNodeInfo {
readonly nodeId: number;
readonly segmentId?: number;
}

interface SpatiallyIndexedSkeletonLayerOptions {
sources2d?: SpatiallyIndexedSkeletonSourceEntry[];
selectedNodeId?: WatchableValueInterface<number | undefined>;
selectedNodeInfo?: WatchableValueInterface<SelectedSkeletonNodeInfo | undefined>;
pendingNodePositionVersion?: WatchableValueInterface<number>;
getPendingNodePosition?: (nodeId: number) => ArrayLike<number> | undefined;
getCachedNode?: (nodeId: number) => SpatiallyIndexedSkeletonNode | undefined;
Expand Down Expand Up @@ -2046,8 +2051,8 @@ export class SpatiallyIndexedSkeletonLayer
computeTextureFormat(new TextureFormat(), dataType, numComponents),
));
}
private selectedNodeId:
| WatchableValueInterface<number | undefined>
private selectedNodeInfo:
| WatchableValueInterface<SelectedSkeletonNodeInfo | undefined>
| undefined;
private pendingNodePositionVersion:
| WatchableValueInterface<number>
Expand Down Expand Up @@ -2127,21 +2132,21 @@ export class SpatiallyIndexedSkeletonLayer
}

private getSelectedNodeOutlineColor() {
const selectedNodeId = this.selectedNodeId?.value;
if (selectedNodeId === undefined) {
const nodeInfo = this.selectedNodeInfo?.value;
if (nodeInfo === undefined) {
return SELECTED_NODE_OUTLINE_FALLBACK_COLOR;
}
const currentGeneration = this.selectedNodeOutlineColorGeneration;
const isCacheValid =
this.cachedSelectedNodeOutlineColorGeneration === currentGeneration;
if (isCacheValid) {
if (this.cachedSelectedNodeOutlineColorGeneration === currentGeneration) {
return this.selectedNodeOutlineColor;
}
const segmentId = this.displayState.segmentSelectionState.baseValue;
const segmentId =
nodeInfo.segmentId !== undefined
? BigInt(nodeInfo.segmentId)
: this.displayState.segmentSelectionState.baseValue;
if (segmentId === undefined) {
return SELECTED_NODE_OUTLINE_FALLBACK_COLOR;
}

this.cachedSelectedNodeOutlineColorGeneration = currentGeneration;
return computeHighVisibilityContrastColor(
this.selectedNodeOutlineColor,
Expand Down Expand Up @@ -2379,7 +2384,7 @@ export class SpatiallyIndexedSkeletonLayer
this.displayState.transform,
),
);
this.selectedNodeId = options.selectedNodeId;
this.selectedNodeInfo = options.selectedNodeInfo;
this.pendingNodePositionVersion = options.pendingNodePositionVersion;
this.getPendingNodePositionOverride = options.getPendingNodePosition;
this.getCachedNodeInfo = options.getCachedNode;
Expand Down Expand Up @@ -2487,10 +2492,9 @@ export class SpatiallyIndexedSkeletonLayer
);
this.nodeIdAttributeIndex = nodeIdIndex >= 0 ? nodeIdIndex : undefined;
const requestRedraw = () => this.redrawNeeded.dispatch();
const selectedNodeWatchable = this.selectedNodeId;
if (selectedNodeWatchable?.changed) {
if (this.selectedNodeInfo?.changed) {
this.registerDisposer(
selectedNodeWatchable.changed.add(() => {
this.selectedNodeInfo.changed.add(() => {
invalidateSelectedNodeOutlineColor();
requestRedraw();
}),
Expand Down Expand Up @@ -2928,7 +2932,7 @@ export class SpatiallyIndexedSkeletonLayer
);
gl.uniform1i(
nodeShader.uniform("uSelectedNodeId"),
this.selectedNodeId?.value ?? -1,
this.selectedNodeInfo?.value?.nodeId ?? -1,
);

const chunkOrigin = vec3.create();
Expand Down Expand Up @@ -3051,7 +3055,7 @@ export class SpatiallyIndexedSkeletonLayer
);
gl.uniform1i(
nodeShader.uniform("uSelectedNodeId"),
this.selectedNodeId?.value ?? -1,
this.selectedNodeInfo?.value?.nodeId ?? -1,
);

if (renderContext.emitPickID) {
Expand Down
16 changes: 5 additions & 11 deletions src/ui/skeleton_edit_tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ describe("spatial_skeleton_edit_tool", () => {
};
const tool = {
layer: {
selectedSpatialSkeletonNodeId: { value: undefined },
selectedSpatialSkeletonNodeInfo: { value: undefined },
spatialSkeletonState: {
mergeAnchorNodeId: { value: 101 },
},
Expand Down Expand Up @@ -736,11 +736,7 @@ describe("spatial_skeleton_edit_tool", () => {
},
},
spatialSkeletonMergeMode: makeModeWatchable(),
selectedSpatialSkeletonNodeId: {
value: selectedNode.nodeId,
changed: makeChangedSignal(),
},
selectedSpatialSkeletonNodeInfo: { value: selectedNode },
selectedSpatialSkeletonNodeInfo: { value: selectedNode, changed: makeChangedSignal() },
spatialSkeletonState: {
mergeAnchorNodeId,
getCachedNode: vi.fn(),
Expand Down Expand Up @@ -834,11 +830,10 @@ describe("spatial_skeleton_edit_tool", () => {
},
},
spatialSkeletonMergeMode: makeModeWatchable(),
selectedSpatialSkeletonNodeId: {
value: selectedNode.nodeId as number | undefined,
selectedSpatialSkeletonNodeInfo: {
value: selectedNode as typeof selectedNode | undefined,
changed: selectedNodeChanged,
},
selectedSpatialSkeletonNodeInfo: { value: selectedNode },
spatialSkeletonState: {
mergeAnchorNodeId,
getCachedNode: vi.fn(),
Expand Down Expand Up @@ -884,7 +879,7 @@ describe("spatial_skeleton_edit_tool", () => {
metaKey: false,
},
});
layer.selectedSpatialSkeletonNodeId.value = undefined;
layer.selectedSpatialSkeletonNodeInfo.value = undefined;
selectedNodeChanged.dispatch();

expect(selectSegment).toHaveBeenCalledWith(17n, true);
Expand Down Expand Up @@ -924,7 +919,6 @@ describe("spatial_skeleton_edit_tool", () => {
},
},
spatialSkeletonSplitMode: makeModeWatchable(),
selectedSpatialSkeletonNodeId: { value: selectedNode.nodeId },
selectedSpatialSkeletonNodeInfo: { value: selectedNode },
spatialSkeletonState: {
commandHistory: new SpatialSkeletonCommandHistory(),
Expand Down
18 changes: 9 additions & 9 deletions src/ui/skeleton_edit_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ abstract class SpatialSkeletonToolBase extends LayerTool<SegmentationUserLayer>
const isVisible = this.isSpatialSkeletonSegmentVisible(pickedSegmentId);
if (isVisible) {
this.removeVisibleSegmentByNumber(pickedSegmentId, { deselect: true });
const selectedNodeId = this.layer.selectedSpatialSkeletonNodeId.value;
const selectedNodeId = this.layer.selectedSpatialSkeletonNodeInfo.value?.nodeId;
const selectedNode =
selectedNodeId === undefined
? undefined
Expand Down Expand Up @@ -421,7 +421,7 @@ abstract class SpatialSkeletonToolBase extends LayerTool<SegmentationUserLayer>
sourceState?: SpatialSkeletonSourceState;
}
| undefined {
const nodeId = this.layer.selectedSpatialSkeletonNodeId.value;
const nodeId = this.layer.selectedSpatialSkeletonNodeInfo.value?.nodeId;
if (
typeof nodeId !== "number" ||
!Number.isSafeInteger(nodeId) ||
Expand Down Expand Up @@ -450,7 +450,7 @@ abstract class SpatialSkeletonToolBase extends LayerTool<SegmentationUserLayer>
}

protected getSelectedSpatialSkeletonNodeSummary() {
const nodeId = this.layer.selectedSpatialSkeletonNodeId.value;
const nodeId = this.layer.selectedSpatialSkeletonNodeInfo.value?.nodeId;
if (nodeId === undefined) {
return undefined;
}
Expand Down Expand Up @@ -533,7 +533,7 @@ abstract class SpatialSkeletonToolBase extends LayerTool<SegmentationUserLayer>
event.detail.preventDefault();
const pinnedSelection = this.layer.manager.root.selectionState.value;
const hasSpatialSkeletonSelection =
this.layer.selectedSpatialSkeletonNodeId.value !== undefined ||
this.layer.selectedSpatialSkeletonNodeInfo.value?.nodeId !== undefined ||
(pinnedSelection?.layers.some(
({ layer, state }) =>
layer === this.layer && hasSpatialSkeletonNodeSelection(state),
Expand Down Expand Up @@ -807,7 +807,7 @@ export class SpatialSkeletonEditModeTool extends SpatialSkeletonToolBase {
layer.spatialSkeletonState.clearPendingNodePositions();
});
activation.registerDisposer(
layer.selectedSpatialSkeletonNodeId.changed.add(renderStatus),
layer.selectedSpatialSkeletonNodeInfo.changed.add(renderStatus),
);
activation.registerDisposer(
layer.manager.root.selectionState.changed.add(renderStatus),
Expand Down Expand Up @@ -862,7 +862,7 @@ export class SpatialSkeletonEditModeTool extends SpatialSkeletonToolBase {
);
return;
}
const selectedParentNodeId = layer.selectedSpatialSkeletonNodeId.value;
const selectedParentNodeId = layer.selectedSpatialSkeletonNodeInfo.value?.nodeId;
const addNodeBlockedReason = this.getAddNodeBlockedReason(
skeletonLayer,
selectedParentNodeId,
Expand Down Expand Up @@ -902,7 +902,7 @@ export class SpatialSkeletonEditModeTool extends SpatialSkeletonToolBase {
return;
}
const selectedParentNodeId =
layer.selectedSpatialSkeletonNodeId.value;
layer.selectedSpatialSkeletonNodeInfo.value?.nodeId;
const addNodeBlockedReason = this.getAddNodeBlockedReason(
skeletonLayer,
selectedParentNodeId,
Expand Down Expand Up @@ -1196,9 +1196,9 @@ export class SpatialSkeletonMergeModeTool extends SpatialSkeletonToolBase {
),
);
activation.registerDisposer(
this.layer.selectedSpatialSkeletonNodeId.changed.add(() => {
this.layer.selectedSpatialSkeletonNodeInfo.changed.add(() => {
if (
this.layer.selectedSpatialSkeletonNodeId.value === undefined &&
this.layer.selectedSpatialSkeletonNodeInfo.value?.nodeId === undefined &&
this.layer.spatialSkeletonState.mergeAnchorNodeId.value !== undefined
) {
anchorSelection = undefined;
Expand Down
8 changes: 4 additions & 4 deletions src/ui/skeleton_tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ export class SpatialSkeletonEditTab extends Tab {
const applyRowInteractionState = (
options: { scrollSelectedIntoView?: boolean } = {},
) => {
const selectedNodeId = layer.selectedSpatialSkeletonNodeId.value;
const selectedNodeId = layer.selectedSpatialSkeletonNodeInfo.value?.nodeId;
nodesList.forEachRenderedItem((entry, index) => {
const item = virtualItems[index];
if (item?.kind !== "node") return;
Expand Down Expand Up @@ -706,7 +706,7 @@ export class SpatialSkeletonEditTab extends Tab {
}

// Extract selected node and segment ID
const selectedId = layer.selectedSpatialSkeletonNodeId.value;
const selectedId = layer.selectedSpatialSkeletonNodeInfo.value?.nodeId;
const selectedNode =
selectedId === undefined
? undefined
Expand Down Expand Up @@ -1178,7 +1178,7 @@ export class SpatialSkeletonEditTab extends Tab {
const entry = document.createElement("div");
entry.className = "neuroglancer-spatial-skeleton-tree-entry";
entry.dataset.selected = String(
node.nodeId === layer.selectedSpatialSkeletonNodeId.value,
node.nodeId === layer.selectedSpatialSkeletonNodeInfo.value?.nodeId,
);
entry.dataset.viewerHovered = String(node.nodeId === hoveredViewerNodeId);
entry.dataset.listHovered = String(node.nodeId === hoveredListNodeId);
Expand Down Expand Up @@ -1717,7 +1717,7 @@ export class SpatialSkeletonEditTab extends Tab {
}, layer.displayState.segmentationColorGroupState),
);
this.registerDisposer(
layer.selectedSpatialSkeletonNodeId.changed.add(() => {
layer.selectedSpatialSkeletonNodeInfo.changed.add(() => {
pendingScrollToSelectedNode = true;
applyRowInteractionState({ scrollSelectedIntoView: true });
}),
Expand Down