Skip to content

[Bug] The brush paints over a locked segment on a volume layer in a stack viewport #2990

Open

Description

@wayfarer3130

Describe the Bug

The brush can paint over a locked segment in a stack viewport when the locked segment is on a labelmap layer that has a volumeId.

#2988 adds getLockedVoxelsOnOtherLayers. The function gives INTERNAL_setValue the voxel managers of the layers that hold a locked segment, other than the edited layer. For a stack edit, the function finds the image of the locked layer by its referenced image, but only when the layer has no volumeId:

let layerTargetOptions = targetOptions;
if (
!isVolumeEdit &&
!layer.volumeId &&
referencedImageId &&
layer.referencedImageIds
) {
const [lockedImageId] = getLabelmapImageIdsForReferencedImageId(
layer,
referencedImageId
);
if (!lockedImageId) {
// The layer has no image for this slice, so nothing is locked here.
return;
}
layerTargetOptions = {
...targetOptions,
imageId: lockedImageId,
sourceLayer: layer,
};
}
const { voxelManager } = resolveLabelmapLayerEditTarget(
layer,
layerTargetOptions
);

When the locked layer has a volumeId, resolveLabelmapLayerEditTarget returns the voxel manager of the whole volume:

function resolveLabelmapLayerEditTarget(
layer: LabelmapLayer,
options: ResolveLabelmapLayerEditTargetOptions = {}
): LabelmapLayerEditTarget {
const imageId = getLabelmapLayerImageId(layer, options);
if (
options.preferVolume ||
layer.volumeId ||
options.viewport instanceof BaseVolumeViewport
) {
const volume = getOrCreateLabelmapVolume(layer);
return {
imageId,
imageData: volume?.imageData as vtkImageData,
voxelManager: volume?.voxelManager as Types.IVoxelManager<number>,
volume,
};
}
const image = imageId ? cache.getImage(imageId) : undefined;
return {
imageId,
voxelManager: image?.voxelManager as Types.IVoxelManager<number>,
image,
};
}

In a stack viewport, INTERNAL_setValue gets a 2D index into the current image. The function then reads the volume with that 2D index, so the function reads slice 0 of the volume and not the current slice. The result:

  • The brush paints over the locked voxels on the current slice.
  • The brush skips voxels that are locked on slice 0 but not on the current slice.

A stack layer gets a geometryVolumeId and not a volumeId (getOrCreateLabelmapVolume), so a normal stack segmentation does not have this problem. The problem occurs for a segmentation that loads as a volume labelmap, when the user then paints in a stack viewport.

Steps to Reproduce

  1. Create a volume labelmap segmentation (the layer has a volumeId) with segments 1, 2 and 3.
  2. Move segment 2 to a layer of its own, for example with segmentation.helpers.moveSegmentToPrivateLabelmap, so that segment 2 is on a different layer from segment 1.
  3. Lock segment 2.
  4. Show the segmentation in a stack viewport, on a slice other than slice 0.
  5. Paint segment 1 across segment 2.

The current behavior

The brush paints segment 1 over the voxels of locked segment 2 on the current slice.

The expected behavior

The brush does not change the voxels of a locked segment, on any layer of the segmentation. This is the user requirement of #2988.

Proposed approach

Fix the stack edits of volume layers in one place, for the brush and for the check of locked voxels together. The stack edit can find the image of each layer by its referenced image, for a layer with a volumeId too, and then read that image with the 2D index. A volume edit continues to read the volume. A unit test can lock a segment on a volume layer, edit a stack slice other than slice 0, and check that the locked voxels stay.

#2988 does not fix this case. The edit of a volume layer in a stack viewport needs a larger change.

OS

Any

Node version

22.17.1

Browser

Any

Activity

  1. linear commented on Oct 15, 2026

    @linear
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions