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
17 changes: 15 additions & 2 deletions src/datasource/catmaid/spatial_skeleton_commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,13 @@ async function commitAndApplyDeleteNode(
if (options.invalidateSourceCells) {
invalidateDeletedNodeSourceCells(resolvedNode.skeletonLayer, deleteContext);
}
const remainingNodes =
layer.spatialSkeletonState.getCachedSegmentNodes(
resolvedNode.node.segmentId,
) ?? [];
if (remainingNodes.length === 0) {
resolvedNode.skeletonLayer.markSegmentEdited(resolvedNode.node.segmentId);
}
return { resolvedNode };
}

Expand Down Expand Up @@ -1853,6 +1860,8 @@ class SplitCommand implements SpatialSkeletonCommand {
[existingSkeletonId, newSkeletonId],
collectUniqueNodePositions(getSplitAffectedNodes(resolvedNode)),
);
resolvedNode.skeletonLayer.retainOverlaySegment(existingSkeletonId);
resolvedNode.skeletonLayer.retainOverlaySegment(newSkeletonId);
StatusMessage.showTemporaryMessage(
`${statusPrefix} skeleton ${existingSkeletonId}. New skeleton: ${newSkeletonId}.`,
);
Expand Down Expand Up @@ -1914,7 +1923,7 @@ class SplitCommand implements SpatialSkeletonCommand {
this.layer.displayState.segmentStatedColors.value.delete(
BigInt(deletedSkeletonId),
);
splitNode.skeletonLayer.suppressBrowseSegment(deletedSkeletonId);
splitNode.skeletonLayer.markSegmentEdited(deletedSkeletonId);
}
this.layer.selectSpatialSkeletonNode(
splitNode.node.nodeId,
Expand All @@ -1932,6 +1941,7 @@ class SplitCommand implements SpatialSkeletonCommand {
formerParent,
),
);
splitNode.skeletonLayer.retainOverlaySegment(resultSkeletonId);
StatusMessage.showTemporaryMessage(
`${statusPrefix} split at node ${splitNode.node.nodeId}.`,
);
Expand Down Expand Up @@ -2118,14 +2128,15 @@ class MergeCommand implements SpatialSkeletonCommand {
BigInt(deletedSkeletonId),
);
if (deletedSkeletonId !== resultSkeletonId) {
firstNode.skeletonLayer.suppressBrowseSegment(deletedSkeletonId);
firstNode.skeletonLayer.markSegmentEdited(deletedSkeletonId);
}
this.layer.clearSpatialSkeletonMergeAnchor();
await refreshTopologySegments(
this.layer,
[resultSkeletonId, deletedSkeletonId],
getMergeAffectedPositions(result.deletedSegmentId, firstNode, secondNode),
);
firstNode.skeletonLayer.retainOverlaySegment(resultSkeletonId);
const swapSuffix = result.directionAdjusted
? " Merge direction was adjusted by the active source."
: "";
Expand Down Expand Up @@ -2223,6 +2234,8 @@ class MergeCommand implements SpatialSkeletonCommand {
`Only the split completed. ${formatErrorMessage(error)}`;
}
}
attachedNode.skeletonLayer.retainOverlaySegment(survivingSegmentId);
attachedNode.skeletonLayer.retainOverlaySegment(restoredSegmentId);
this.layer.selectSpatialSkeletonNode(
attachedNode.node.nodeId,
this.layer.manager.root.selectionState.pin.value,
Expand Down
28 changes: 18 additions & 10 deletions src/layer/segmentation/spatial_skeleton_commands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1247,7 +1247,8 @@ describe("spatial_skeleton_commands", () => {
source: skeletonSource,
getNode: vi.fn((nodeId: number) => cacheByNode.get(nodeId)),
invalidateSourceCellsForPositions: vi.fn(),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
};
const layer = {
displayState: makeDisplayState([originalSegmentId]),
Expand Down Expand Up @@ -1386,7 +1387,8 @@ describe("spatial_skeleton_commands", () => {
source: skeletonSource,
getNode: vi.fn((nodeId: number) => cacheByNode.get(nodeId)),
invalidateSourceCellsForPositions: vi.fn(),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
};
const layer = {
displayState: {
Expand Down Expand Up @@ -1437,7 +1439,7 @@ describe("spatial_skeleton_commands", () => {
segmentId: originalSegmentId,
});

skeletonLayer.suppressBrowseSegment.mockClear();
skeletonLayer.markSegmentEdited.mockClear();
deleteSegmentColor.mockClear();
layer.selectSpatialSkeletonNode.mockClear();
layer.markSpatialSkeletonNodeDataChanged.mockClear();
Expand All @@ -1458,7 +1460,7 @@ describe("spatial_skeleton_commands", () => {
}),
);
expect(deleteSegmentColor).toHaveBeenCalledWith(BigInt(splitSegmentId));
expect(skeletonLayer.suppressBrowseSegment).toHaveBeenCalledWith(
expect(skeletonLayer.markSegmentEdited).toHaveBeenCalledWith(
splitSegmentId,
);
expect(layer.selectSpatialSkeletonNode).toHaveBeenCalledWith(
Expand Down Expand Up @@ -1607,7 +1609,8 @@ describe("spatial_skeleton_commands", () => {
source: skeletonSource,
getNode: vi.fn((nodeId: number) => cacheByNode.get(nodeId)),
invalidateSourceCellsForPositions: vi.fn(),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
};
const layer = {
displayState: {
Expand Down Expand Up @@ -1860,7 +1863,8 @@ describe("spatial_skeleton_commands", () => {
source: skeletonSource,
getNode: vi.fn((nodeId: number) => cacheByNode.get(nodeId)),
invalidateSourceCellsForPositions: vi.fn(),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
};
const layer = {
displayState: {
Expand Down Expand Up @@ -2150,7 +2154,8 @@ describe("spatial_skeleton_commands", () => {
source: skeletonSource,
getNode: vi.fn((nodeId: number) => cacheByNode.get(nodeId)),
invalidateSourceCellsForPositions: vi.fn(),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
};
const layer = {
displayState: {
Expand Down Expand Up @@ -2349,7 +2354,8 @@ describe("spatial_skeleton_commands", () => {
source: skeletonSource,
getNode: vi.fn((nodeId: number) => cacheByNode.get(nodeId)),
invalidateSourceCellsForPositions: vi.fn(),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
};
const layer = {
displayState: {
Expand Down Expand Up @@ -2509,7 +2515,8 @@ describe("spatial_skeleton_commands", () => {
source: skeletonSource,
getNode: vi.fn((nodeId: number) => cacheByNode.get(nodeId)),
invalidateSourceCellsForPositions: vi.fn(),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
};
const layer = {
displayState: makeDisplayState([firstSegmentId, secondSegmentId]),
Expand Down Expand Up @@ -2603,7 +2610,8 @@ describe("spatial_skeleton_commands", () => {
if (nodeId === secondNode.nodeId) return secondNode;
return undefined;
}),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
invalidateSourceCellsForPositions: vi.fn(),
};
const layer = {
Expand Down
7 changes: 3 additions & 4 deletions src/skeleton/frontend.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,19 +299,18 @@ describe("SpatiallyIndexedSkeletonLayer targeted source invalidation", () => {
});

describe("SpatiallyIndexedSkeletonLayer browse exclusions", () => {
it("includes suppressed browse segments even when no overlay segment is loaded", () => {
it("includes edited segments in browse exclusions regardless of cache state", () => {
const layer = Object.assign(
Object.create(SpatiallyIndexedSkeletonLayer.prototype),
{
suppressedBrowseSegmentIds: new Set<number>(),
editedSegmentIds: new Set<number>(),
browseExcludedSegments: new Uint64Set(),
browseExcludedSegmentsKey: undefined,
redrawNeeded: { dispatch: vi.fn() },
getLoadedOverlaySegmentIds: () => [],
},
);

expect(layer.suppressBrowseSegment(29)).toBe(true);
expect(layer.markSegmentEdited(29)).toBe(true);
expect(layer.redrawNeeded.dispatch).toHaveBeenCalledTimes(1);

const excludedSegments = (layer as any).getBrowsePassExcludedSegments();
Expand Down
44 changes: 6 additions & 38 deletions src/skeleton/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2071,7 +2071,7 @@ export class SpatiallyIndexedSkeletonLayer
private browseExcludedSegments = new Uint64Set();
private gpuBrowseExcludedSegmentsHashTable: GPUHashTable<HashSetUint64>;
private browseExcludedSegmentsKey: string | undefined;
private suppressedBrowseSegmentIds = new Set<number>();
private readonly editedSegmentIds = new Set<number>();
private retainedOverlaySegmentIds: number[] = [];
private maxRetainedOverlaySegments: number;
private readonly selectedNodeOutlineColor = vec3.clone(
Expand Down Expand Up @@ -2159,6 +2159,7 @@ export class SpatiallyIndexedSkeletonLayer
}

retainOverlaySegment(segmentId: number) {
this.markSegmentEdited(segmentId);
const nextRetainedOverlaySegmentIds =
retainSpatiallyIndexedSkeletonOverlaySegment(
this.retainedOverlaySegmentIds,
Expand All @@ -2180,16 +2181,16 @@ export class SpatiallyIndexedSkeletonLayer
return true;
}

suppressBrowseSegment(segmentId: number) {
markSegmentEdited(segmentId: number) {
const normalizedSegmentId = Math.round(Number(segmentId));
if (
!Number.isSafeInteger(normalizedSegmentId) ||
normalizedSegmentId <= 0 ||
this.suppressedBrowseSegmentIds.has(normalizedSegmentId)
this.editedSegmentIds.has(normalizedSegmentId)
) {
return false;
}
this.suppressedBrowseSegmentIds.add(normalizedSegmentId);
this.editedSegmentIds.add(normalizedSegmentId);
this.redrawNeeded.dispatch();
return true;
}
Expand All @@ -2201,41 +2202,8 @@ export class SpatiallyIndexedSkeletonLayer
);
}

private getLoadedOverlaySegmentIds(
segmentIds: readonly number[] = this.getOverlayRenderSegmentIds(),
) {
if (this.inspectionState === undefined) {
return [];
}
return segmentIds.filter(
(segmentId) =>
this.inspectionState?.getCachedSegmentNodes(segmentId) !== undefined,
);
}

private getNormalizedBrowsePassExcludedSegmentIds() {
const segmentIds = new Set<number>();
for (const segmentId of this.getLoadedOverlaySegmentIds()) {
const normalizedSegmentId = Math.round(Number(segmentId));
if (
!Number.isSafeInteger(normalizedSegmentId) ||
normalizedSegmentId <= 0
) {
continue;
}
segmentIds.add(normalizedSegmentId);
}
for (const segmentId of this.suppressedBrowseSegmentIds) {
const normalizedSegmentId = Math.round(Number(segmentId));
if (
!Number.isSafeInteger(normalizedSegmentId) ||
normalizedSegmentId <= 0
) {
continue;
}
segmentIds.add(normalizedSegmentId);
}
return [...segmentIds].sort((a, b) => a - b);
return [...this.editedSegmentIds].sort((a, b) => a - b);
}

private getBrowsePassExcludedSegments() {
Expand Down
5 changes: 3 additions & 2 deletions src/ui/skeleton_edit_tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@ describe("spatial_skeleton_edit_tool", () => {
if (nodeId === secondNode.nodeId) return secondNode;
return undefined;
}),
suppressBrowseSegment: vi.fn(),
markSegmentEdited: vi.fn(),
retainOverlaySegment: vi.fn(),
invalidateSourceCellsForPositions: vi.fn(),
};
const commandHistory = new SpatialSkeletonCommandHistory();
Expand Down Expand Up @@ -634,7 +635,7 @@ describe("spatial_skeleton_edit_tool", () => {
segmentId: 17,
});
expect(deleteSegmentColor).toHaveBeenCalledWith(11n);
expect(skeletonLayer.suppressBrowseSegment).toHaveBeenCalledWith(11);
expect(skeletonLayer.markSegmentEdited).toHaveBeenCalledWith(11);
expect(markSpatialSkeletonNodeDataChanged).toHaveBeenCalledWith({
invalidateFullSkeletonCache: false,
});
Expand Down
1 change: 1 addition & 0 deletions src/ui/skeleton_edit_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,7 @@ export class SpatialSkeletonEditModeTool extends SpatialSkeletonToolBase {
)
return;
dragStarted = true;
skeletonLayer.markSegmentEdited(nodeInfo.segmentId);
}
dragPanel.translateDataPointByViewportPixels(
this.dragGlobalPosition,
Expand Down