Skip to content

Conversation

@briossant
Copy link

@briossant briossant commented Oct 30, 2025

This Draft Pull Request introduces an interactive voxel annotation feature, allowing users to perform manual segmentation by painting directly onto volumetric layers. This implementation is based on the proposal in Issue #851 and incorporates the feedback from @jbms.

Key Changes & Architectural Overview

Following the discussion, this implementation has been significantly revised from the initial prototype:

  1. Integration with Existing Layers: Instead of a new vox layer type, the voxel editing functionality is now integrated directly into ImageUserLayer and SegmentationUserLayer via a UserLayerWithVoxelEditingMixin. This mixin adds a new "Draw" tab in the UI.
draw tab
  1. New Tool System: The Brush and Flood Fill tools are implemented as toggleable LayerTools, while the Picker tool is a one-shot tool. All integrate with Neuroglancer's new tool system. The drawing action is bound to Ctrl + Left Click.

  2. Optimistic Preview for Compressed Chunks: To provide immediate visual feedback and solve the performance problem with compressed chunks, edits are now rendered through an optimistic preview layer.

  • When a user paints, edits are first applied to an InMemoryVolumeChunkSource.
  • This preview source is rendered by a second instance of the layer's primary RenderLayer (e.g., ImageRenderLayer or SegmentationRenderLayer). This ensures the preview perfectly matches the user's existing shader and display settings.
  • The base data chunk is not modified on the frontend, avoiding the need to decompress/recompress it.

Data-flow

sequenceDiagram
participant User
participant Tool as VoxelBrushTool
participant ControllerFE as VoxelEditController (FE)
participant EditSourceFE as OverlayChunkSource (FE)
participant BaseSourceFE as VolumeChunkSource (FE)
participant ControllerBE as VoxelEditController (BE)
participant BaseSourceBE as VolumeChunkSource (BE)

    User->>Tool: Mouse Down/Drag
    Tool->>ControllerFE: paintBrushWithShape(mouse, ...)
    ControllerFE->>ControllerFE: Calculates affected voxels and chunks

    ControllerFE->>EditSourceFE: applyLocalEdits(chunkKeys, ...)
    activate EditSourceFE
    EditSourceFE->>EditSourceFE: Modifies its own in-memory chunk data
    note over EditSourceFE: This chunk's texture is re-uploaded to the GPU
    deactivate EditSourceFE

    ControllerFE->>ControllerBE: commitEdits(edits, ...) [RPC]

    activate ControllerBE
    ControllerBE->>ControllerBE: Debounces and batches edits
    ControllerBE->>BaseSourceBE: applyEdits(chunkKeys, ...)
    activate BaseSourceBE
    BaseSourceBE-->>ControllerBE: Returns VoxelChange (for undo stack)
    deactivate BaseSourceBE
    ControllerBE->>ControllerFE: callChunkReload(chunkKeys) [RPC]
    activate ControllerFE
    ControllerFE->>BaseSourceFE: invalidateChunks(chunkKeys)
    note over BaseSourceFE: BaseSourceFE re-fetches chunk with the now-permanent edit.
    ControllerFE->>EditSourceFE: clearOptimisticChunk(chunkKeys)
    deactivate ControllerFE

    ControllerBE->>ControllerBE: Pushes change to Undo Stack & enqueues for downsampling
    deactivate ControllerBE

    loop Downsampling & Reload Cascade
        ControllerBE->>ControllerBE: downsampleStep(chunkKeys)
        ControllerBE->>ControllerFE: callChunkReload(chunkKeys) [RPC]
        activate ControllerFE
        ControllerFE->>BaseSourceFE: invalidateChunks(chunkKeys)
        note over BaseSourceFE: BaseSourceFE re-fetches chunk with the now-permanent edit.
        ControllerFE->>EditSourceFE: clearOptimisticChunk(chunkKeys)
        deactivate ControllerFE
    end
Loading
  1. Writable source selection Aside every activated volume sub-sources of a writable datasource, an additional checkbox lets the user mark the sub-source as writable, then neuroglancer will try to write in it.
writable source

Data sources & Kvstores

Currently, there is a very limited set of supported data sources and kvstores, which are:

  • datasources:
    • zarr with no compression
  • kvstores:
    • s3+http: mostly used for local development, paired with minio
    • opfs: in-browser storage, also used for local development at some point, the relevancy can be discussed.
    • ssa+https: a kvstore linked to an in development project, which is a stateless (thanks to OAuth 2.0) worker providing signed urls to read/write in s3 stores

This lack of support is the first limitation of the current implementation.

Open Questions & Future Work

This PR focuses on establishing the core architecture. Several larger topics from the original discussion are noted here as future work:

  • Efficient Low-Resolution Drawing: As discussed, efficient, multi-resolution drawing with upsampling is a complex challenge that requires a new data format.
  • 3D Drawing Tools: As suggested by @fcollman, 3D-specific tools like interpolation between slices are out of scope for this initial PR but could be a valuable direction for future work.

Checklist

  • Completed the todo list found in src/voxel_annotations/TODOs.md
  • Added support to more (every?) datasources and kvstores
  • Signed the CLA.

@google-cla
Copy link

google-cla bot commented Oct 30, 2025

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@jbms
Copy link
Collaborator

jbms commented Nov 3, 2025

Can you complete the CLA?

this.voxBrushShape.changed.add(this.specificationChanged.dispatch);
this.voxFloodMaxVoxels.changed.add(this.specificationChanged.dispatch);
this.paintValue.changed.add(this.specificationChanged.dispatch);
this.tabs.add("Draw", {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the hidden property to ensure this is only visible if there is a writable source.

}),
},
{
label: "Paint Color",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is intended to show the color for segmentation layers, it shouldn't be editable, and also it doesn't always seem to stay in sync.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this is pretty much useless, I will remove it

control.title = "Specify segment ID or intensity value to paint";
control.addEventListener("change", () => {
try {
layer.setVoxelPaintValue(BigInt(control.value));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be limited to the valid range for the data type.

@jbms
Copy link
Collaborator

jbms commented Nov 3, 2025

The brush hover outline (circle where the mouse pointer is) seems to go away in some cases when changing the zoom level.

@briossant briossant force-pushed the feature/voxel-annotation branch from 2d5359d to 9d15526 Compare November 10, 2025 09:51
@briossant
Copy link
Author

briossant commented Nov 10, 2025

I need to rewrite history, my commits are signed with the wrong email, I will open a new PR I made a mistake with this force push nevermind its fixed

- Introduced a new dummy `MultiscaleVolumeChunkSource`.
- Added `VoxelAnnotationRenderLayer` for voxel annotation rendering.
- Implemented `VoxUserLayer` with dummy data source and rendering.
- Added tools and logs for voxel layer interactions and debugging.
- Documented voxel annotation specification and implementation details.
- Added a backend `VoxDummyChunkSource` that generates a checkerboard pattern for voxel annotations.
- Implemented frontend `VoxDummyChunkSource` with RPC pairing to the backend.
- Updated documentation with details on chunk source architecture and implementation.
…s to corruped the chunk after the usage of the tool. Added a front end buffer which is the only drawing storage for now. Added user settings to set the voxel_annotation layer scale and bounds. Added a second empty source to DummyMultiscaleVolumeChunkSource to prevent crashs when zoomed out too much
…lobal one (there where a missing convertion) ; add a primitive brush tool
…r remote workflows, label creation, and new drawing tools
…n unnecessary amount of 0 filled chunks, this fixed the perfomance issue
… local coord space was not aligned with the global (e.g. x and z inverted for example, which is common).
…tion on cursor sizes (128 for chrome), to fix this, the brush cursor is now drawn on an overlay canvas. This explains why other web-based drawing tools have inertia (we now have) on the brush cursor.
…e the value to the valid range for the data type.
…for the entire downsample cascade before reloading chunks, this fixes the disappearing preview at low res.
@briossant briossant force-pushed the feature/voxel-annotation branch from 9d15526 to 3a7ebbc Compare November 10, 2025 10:08
…ering on chunk reload, the reloading pipeline was reverted for the primaryRenderLayer and the new logic only kept for the optimistic one.
… through volumes (writable or not) in the goal of supporting a "writing over read-only sources" workflow
}

async writeChunk(chunk: VolumeChunk): Promise<void> {
const { kvStore, getChunkKey, decodeCodecs } = this.chunkKvStore as any;
Copy link
Contributor

@chrisj chrisj Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid casting as any, it makes it hard to follow. kvStore in zarr is explictly a ReadableKvStore so I think you want to change that to a normal KvStore.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I will look into that. I originally did this as part of my non-destructive workflow (e.g. I wanted to modify as little as possible of preexisting neuroglancer code while prototyping), but this makes no more sense now.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants