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
37 changes: 7 additions & 30 deletions src/layer/segmentation/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ const { SegmentationUserLayer } = await import(
const {
PerspectiveViewSpatiallyIndexedSkeletonLayer,
SliceViewPanelSpatiallyIndexedSkeletonLayer,
SliceViewSpatiallyIndexedSkeletonLayer,
MultiscaleSliceViewSpatiallyIndexedSkeletonLayer,
} = await import("#src/skeleton/frontend.js");

const { SegmentSelectionState } = await import(
Expand Down Expand Up @@ -81,30 +79,14 @@ function makeSpatialSkeletonLayerWithSource(source: unknown) {

describe("layer/segmentation spatial skeleton chunk stats", () => {
it("tracks combined chunk load state from the loading render layers only", () => {
// After the 2D/3D backend unification, only PerspectiveViewSpatiallyIndexedSkeletonLayer
// contributes to the chunk stats (it handles both 2D and 3D views via the shared backend).
const perspectiveLayer = Object.assign(
Object.create(PerspectiveViewSpatiallyIndexedSkeletonLayer.prototype),
{
layerChunkProgressInfo: {
numVisibleChunksNeeded: 5,
numVisibleChunksAvailable: 3,
},
},
);
const sliceLayer = Object.assign(
Object.create(SliceViewSpatiallyIndexedSkeletonLayer.prototype),
{
layerChunkProgressInfo: {
numVisibleChunksNeeded: 4,
numVisibleChunksAvailable: 2,
},
},
);
const multiscaleSliceLayer = Object.assign(
Object.create(MultiscaleSliceViewSpatiallyIndexedSkeletonLayer.prototype),
{
layerChunkProgressInfo: {
numVisibleChunksNeeded: 6,
numVisibleChunksAvailable: 5,
numVisibleChunksNeeded: 9,
numVisibleChunksAvailable: 7,
},
},
);
Expand All @@ -121,12 +103,7 @@ describe("layer/segmentation spatial skeleton chunk stats", () => {
const layer = Object.assign(
Object.create(SegmentationUserLayer.prototype),
{
renderLayers: [
perspectiveLayer,
sliceLayer,
multiscaleSliceLayer,
slicePanelLayer,
],
renderLayers: [perspectiveLayer, slicePanelLayer],
spatialSkeletonVisibleChunksNeeded: new WatchableValue(0),
spatialSkeletonVisibleChunksAvailable: new WatchableValue(0),
spatialSkeletonVisibleChunksLoaded: new WatchableValue(false),
Expand All @@ -136,8 +113,8 @@ describe("layer/segmentation spatial skeleton chunk stats", () => {

layer.updateSpatialSkeletonChunkLoadState();

expect(layer.spatialSkeletonVisibleChunksNeeded.value).toBe(15);
expect(layer.spatialSkeletonVisibleChunksAvailable.value).toBe(10);
expect(layer.spatialSkeletonVisibleChunksNeeded.value).toBe(9);
expect(layer.spatialSkeletonVisibleChunksAvailable.value).toBe(7);
expect(layer.spatialSkeletonVisibleChunksLoaded.value).toBe(false);
});
});
Expand Down
45 changes: 11 additions & 34 deletions src/layer/segmentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,9 @@ import {
SliceViewPanelSkeletonLayer,
PerspectiveViewSpatiallyIndexedSkeletonLayer,
SliceViewPanelSpatiallyIndexedSkeletonLayer,
SliceViewSpatiallyIndexedSkeletonLayer,
SpatiallyIndexedSkeletonLayer,
SpatiallyIndexedSkeletonSource,
MultiscaleSpatiallyIndexedSkeletonSource,
MultiscaleSliceViewSpatiallyIndexedSkeletonLayer,
} from "#src/skeleton/frontend.js";
import {
classifySpatialSkeletonDisplayNodeType as getSpatialSkeletonDisplayNodeType,
Expand Down Expand Up @@ -1409,8 +1407,7 @@ export class SegmentationUserLayer extends Base {
!layers.some(
(layer) =>
(layer instanceof PerspectiveViewSpatiallyIndexedSkeletonLayer ||
layer instanceof SliceViewPanelSpatiallyIndexedSkeletonLayer ||
layer instanceof SliceViewSpatiallyIndexedSkeletonLayer) &&
layer instanceof SliceViewPanelSpatiallyIndexedSkeletonLayer) &&
getSpatiallyIndexedSkeletonSource(layer.base) !== undefined,
),
{ changed: this.layersChanged, value: this.renderLayers },
Expand Down Expand Up @@ -1510,30 +1507,18 @@ export class SegmentationUserLayer extends Base {
if (layer instanceof SliceViewPanelSpatiallyIndexedSkeletonLayer) {
return layer.base;
}
if (layer instanceof SliceViewSpatiallyIndexedSkeletonLayer) {
return layer.base;
}
}
return undefined;
};

getSpatialSkeletonChunkStats(kind: "2d" | "3d") {
// 2D chunks are now handled by the same backend as 3D, so only report
// under "3d" to avoid double-counting in updateSpatialSkeletonChunkLoadState.
if (kind === "2d") return { presentCount: 0, totalCount: 0 };
let needed = 0;
let available = 0;
for (const layer of this.renderLayers) {
if (
kind === "3d" &&
layer instanceof PerspectiveViewSpatiallyIndexedSkeletonLayer
) {
needed += layer.layerChunkProgressInfo.numVisibleChunksNeeded;
available += layer.layerChunkProgressInfo.numVisibleChunksAvailable;
continue;
}
if (
kind === "2d" &&
(layer instanceof SliceViewSpatiallyIndexedSkeletonLayer ||
layer instanceof MultiscaleSliceViewSpatiallyIndexedSkeletonLayer)
) {
if (layer instanceof PerspectiveViewSpatiallyIndexedSkeletonLayer) {
needed += layer.layerChunkProgressInfo.numVisibleChunksNeeded;
available += layer.layerChunkProgressInfo.numVisibleChunksAvailable;
}
Expand Down Expand Up @@ -1568,8 +1553,7 @@ export class SegmentationUserLayer extends Base {
for (const layer of this.renderLayers) {
if (
layer instanceof PerspectiveViewSpatiallyIndexedSkeletonLayer ||
layer instanceof SliceViewPanelSpatiallyIndexedSkeletonLayer ||
layer instanceof SliceViewSpatiallyIndexedSkeletonLayer
layer instanceof SliceViewPanelSpatiallyIndexedSkeletonLayer
) {
hasSpatialSkeletonLayer = true;
break;
Expand All @@ -1587,8 +1571,7 @@ export class SegmentationUserLayer extends Base {
for (const layer of this.renderLayers) {
if (
layer instanceof PerspectiveViewSpatiallyIndexedSkeletonLayer ||
layer instanceof SliceViewPanelSpatiallyIndexedSkeletonLayer ||
layer instanceof SliceViewSpatiallyIndexedSkeletonLayer
layer instanceof SliceViewPanelSpatiallyIndexedSkeletonLayer
) {
return layer.base.source;
}
Expand Down Expand Up @@ -1802,13 +1785,6 @@ export class SegmentationUserLayer extends Base {
),
);
} else if (mesh instanceof MultiscaleSpatiallyIndexedSkeletonSource) {
const base = new MultiscaleSliceViewSpatiallyIndexedSkeletonLayer(
this.manager.chunkManager,
mesh,
displayState,
);
loadedSubsource.addRenderLayer(base);

const perspectiveSources = mesh.getPerspectiveSources();
const slicePanelSources = mesh.getSliceViewPanelSources();
const sharedSpatialSkeletonSources =
Expand All @@ -1825,6 +1801,8 @@ export class SegmentationUserLayer extends Base {
{
gridLevel: displayState.spatialSkeletonGridLevel3d,
lod: displayState.skeletonLod,
gridLevel2d: displayState.spatialSkeletonGridLevel2d,
lod2d: displayState.spatialSkeletonLod2d,
sources2d: slicePanelSources,
selectedNodeId: this.selectedSpatialSkeletonNodeId,
pendingNodePositionVersion:
Expand Down Expand Up @@ -1861,6 +1839,8 @@ export class SegmentationUserLayer extends Base {
{
gridLevel: displayState.spatialSkeletonGridLevel3d,
lod: displayState.skeletonLod,
gridLevel2d: displayState.spatialSkeletonGridLevel2d,
lod2d: displayState.spatialSkeletonLod2d,
selectedNodeId: this.selectedSpatialSkeletonNodeId,
pendingNodePositionVersion:
this.spatialSkeletonState.pendingNodePositionVersion,
Expand All @@ -1874,9 +1854,6 @@ export class SegmentationUserLayer extends Base {
loadedSubsource.addRenderLayer(
new PerspectiveViewSpatiallyIndexedSkeletonLayer(base.addRef()),
);
loadedSubsource.addRenderLayer(
new SliceViewSpatiallyIndexedSkeletonLayer(base.addRef()),
);
loadedSubsource.addRenderLayer(
new SliceViewPanelSpatiallyIndexedSkeletonLayer(
/* transfer ownership */ base,
Expand Down
118 changes: 21 additions & 97 deletions src/skeleton/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import {
SKELETON_LAYER_RPC_ID,
SPATIALLY_INDEXED_SKELETON_RENDER_LAYER_RPC_ID,
SPATIALLY_INDEXED_SKELETON_RENDER_LAYER_UPDATE_SOURCES_RPC_ID,
SPATIALLY_INDEXED_SKELETON_SLICEVIEW_RENDER_LAYER_RPC_ID,
} from "#src/skeleton/base.js";
import {
freeSkeletonChunkSystemMemory,
Expand All @@ -61,11 +60,9 @@ import {
SCALE_PRIORITY_MULTIPLIER,
SliceViewChunk,
SliceViewChunkSourceBackend,
SliceViewRenderLayerBackend,
} from "#src/sliceview/backend.js";
import {
forEachVisibleVolumetricChunk,
type SliceViewBase,
type SliceViewChunkSpecification,
type SliceViewProjectionParameters,
type TransformedSource,
Expand Down Expand Up @@ -368,6 +365,8 @@ export class SpatiallyIndexedSkeletonRenderLayerBackend extends withChunkManager
renderScaleTarget: SharedWatchableValue<number>;
skeletonLod: SharedWatchableValue<number>;
skeletonGridLevel: SharedWatchableValue<number>;
skeletonLod2d: SharedWatchableValue<number>;
skeletonGridLevel2d: SharedWatchableValue<number>;
private pendingLodCleanup = false;

constructor(rpc: RPC, options: any) {
Expand All @@ -376,6 +375,8 @@ export class SpatiallyIndexedSkeletonRenderLayerBackend extends withChunkManager
this.localPosition = rpc.get(options.localPosition);
this.skeletonLod = rpc.get(options.skeletonLod);
this.skeletonGridLevel = rpc.get(options.skeletonGridLevel);
this.skeletonLod2d = rpc.get(options.skeletonLod2d);
this.skeletonGridLevel2d = rpc.get(options.skeletonGridLevel2d);
const scheduleUpdateChunkPriorities = () =>
this.chunkManager.scheduleUpdateChunkPriorities();
this.registerDisposer(
Expand All @@ -387,19 +388,22 @@ export class SpatiallyIndexedSkeletonRenderLayerBackend extends withChunkManager
this.registerDisposer(
this.skeletonGridLevel.changed.add(scheduleUpdateChunkPriorities),
);
this.registerDisposer(
this.skeletonGridLevel2d.changed.add(scheduleUpdateChunkPriorities),
);

// Debounce LOD changes to avoid making requests for every slider value
const debouncedLodUpdate = debounce(() => {
scheduleUpdateChunkPriorities();
}, SPATIALLY_INDEXED_SKELETON_LOD_DEBOUNCE_MS);
this.registerDisposer(() => debouncedLodUpdate.cancel());

this.registerDisposer(
this.skeletonLod.changed.add(() => {
this.pendingLodCleanup = true;
debouncedLodUpdate();
}),
);
const onLodChanged = () => {
this.pendingLodCleanup = true;
debouncedLodUpdate();
};
this.registerDisposer(this.skeletonLod.changed.add(onLodChanged));
this.registerDisposer(this.skeletonLod2d.changed.add(onLodChanged));
this.registerDisposer(
this.chunkManager.recomputeChunkPriorities.add(() =>
this.recomputeChunkPriorities(),
Expand Down Expand Up @@ -514,7 +518,10 @@ export class SpatiallyIndexedSkeletonRenderLayerBackend extends withChunkManager
}
}
const renderScaleTarget = this.renderScaleTarget.value;
const skeletonGridLevel = this.skeletonGridLevel.value;
const is2dView = pixelSize !== undefined;
const skeletonGridLevel = (
is2dView ? this.skeletonGridLevel2d : this.skeletonGridLevel
).value;

const selectScales = (
scales: TransformedSource<
Expand Down Expand Up @@ -606,7 +613,7 @@ export class SpatiallyIndexedSkeletonRenderLayerBackend extends withChunkManager
return selected;
};

const lodValue = this.skeletonLod.value;
const lodValue = (is2dView ? this.skeletonLod2d : this.skeletonLod).value;
for (const scales of transformedSources) {
const selectedScales = selectScales(scales);
for (const { tsource, scaleIndex } of selectedScales) {
Expand All @@ -624,8 +631,9 @@ export class SpatiallyIndexedSkeletonRenderLayerBackend extends withChunkManager
basePriority + SCALE_PRIORITY_MULTIPLIER * scaleIndex;
source.currentLod = lodValue;
source.currentRequestGeneration = currentGeneration;
source.currentRequestOwner =
SpatiallyIndexedSkeletonChunkRequestOwner.VIEW_3D;
source.currentRequestOwner = is2dView
? SpatiallyIndexedSkeletonChunkRequestOwner.VIEW_2D
: SpatiallyIndexedSkeletonChunkRequestOwner.VIEW_3D;
forEachVisibleVolumetricChunk(
projectionParameters,
this.localPosition.value,
Expand Down Expand Up @@ -653,87 +661,3 @@ export class SpatiallyIndexedSkeletonRenderLayerBackend extends withChunkManager
}
}
}

@registerSharedObject(SPATIALLY_INDEXED_SKELETON_SLICEVIEW_RENDER_LAYER_RPC_ID)
export class SpatiallyIndexedSkeletonSliceViewRenderLayerBackend extends SliceViewRenderLayerBackend {
skeletonGridLevel: SharedWatchableValue<number>;
skeletonLod: SharedWatchableValue<number>;
private chunkManager_: ChunkManager;
private pendingLodCleanup = false;
private trackedSources = new Set<SpatiallyIndexedSkeletonSourceBackend>();

constructor(rpc: RPC, options: any) {
super(rpc, options);
this.skeletonGridLevel = rpc.get(options.skeletonGridLevel);
this.skeletonLod = rpc.get(options.skeletonLod);
const chunkManager = rpc.get(options.chunkManager);
this.chunkManager_ = chunkManager;
const scheduleUpdateChunkPriorities = () =>
chunkManager.scheduleUpdateChunkPriorities();
this.registerDisposer(
this.skeletonGridLevel.changed.add(scheduleUpdateChunkPriorities),
);
// Debounce LOD changes to avoid making requests for every slider value.
const debouncedLodUpdate = debounce(() => {
scheduleUpdateChunkPriorities();
}, SPATIALLY_INDEXED_SKELETON_LOD_DEBOUNCE_MS);
this.registerDisposer(() => debouncedLodUpdate.cancel());

this.registerDisposer(
this.skeletonLod.changed.add(() => {
this.pendingLodCleanup = true;
debouncedLodUpdate();
}),
);
this.registerDisposer(
chunkManager.recomputeChunkPrioritiesLate.add(() => {
if (!this.pendingLodCleanup) return;
cancelStaleSpatiallyIndexedSkeletonDownloads(
chunkManager,
this.trackedSources,
chunkManager.recomputeChunkPriorities.count,
);
this.pendingLodCleanup = false;
}),
);
}

prepareChunkSourceForRequest(source: SpatiallyIndexedSkeletonSourceBackend) {
this.trackedSources.add(source);
source.currentLod = this.skeletonLod.value;
source.currentRequestGeneration =
this.chunkManager_.recomputeChunkPriorities.count;
source.currentRequestOwner =
SpatiallyIndexedSkeletonChunkRequestOwner.VIEW_2D;
}

filterVisibleSources(
sliceView: SliceViewBase,
sources: readonly TransformedSource[],
): Iterable<TransformedSource> {
const lodValue = this.skeletonLod.value;
for (const tsource of sources) {
const source = tsource.source as SpatiallyIndexedSkeletonSourceBackend;
this.trackedSources.add(source);
source.currentLod = lodValue;
source.currentRequestGeneration =
this.chunkManager_.recomputeChunkPriorities.count;
source.currentRequestOwner =
SpatiallyIndexedSkeletonChunkRequestOwner.VIEW_2D;
}

if (
sources.length > 0 &&
sources.every(
(source) => getSpatiallyIndexedSkeletonGridIndex(source) !== undefined,
)
) {
return selectSpatiallyIndexedSkeletonEntriesByGrid(
sources,
this.skeletonGridLevel.value,
getSpatiallyIndexedSkeletonGridIndex,
);
}
return super.filterVisibleSources(sliceView, sources);
}
}
2 changes: 0 additions & 2 deletions src/skeleton/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export const SPATIALLY_INDEXED_SKELETON_RENDER_LAYER_RPC_ID =
"skeleton/SpatiallyIndexedSkeletonRenderLayer";
export const SPATIALLY_INDEXED_SKELETON_RENDER_LAYER_UPDATE_SOURCES_RPC_ID =
"skeleton/SpatiallyIndexedSkeletonRenderLayer.updateSources";
export const SPATIALLY_INDEXED_SKELETON_SLICEVIEW_RENDER_LAYER_RPC_ID =
"skeleton/SpatiallyIndexedSkeletonSliceViewRenderLayer";

export interface VertexAttributeInfo {
dataType: DataType;
Expand Down
Loading
Loading