Skip to content

Keep touching objects apart when an AI annotation's image is finer than its voxels - #141

Merged
davidackerman merged 1 commit into
mainfrom
ai-annotate-split-before-shrink
Oct 19, 2026
Merged

davidackerman merged 1 commit into
mainfrom
ai-annotate-split-before-shrink

Conversation

@davidackerman

Copy link
Copy Markdown
Collaborator

This is part of the batch, #137. Merging #137 with a merge commit marks this PR merged too.

AI-annotate asks the model to leave a thin black line between objects that touch. On Accept, the mask was shrunk to the annotation voxels first and cut into objects after. When the image's pixels are finer than the voxels, each voxel takes only one pixel in every two, so the line could fall between the pixels taken and two objects were written as one id:

image (8 nm):   ■ ■ ■ □ ■ ■ ■ ■     □ = the black line
pixels taken:     ■   ■   ■   ■     the line falls between them
saved (16 nm):    ■   ■   ■   ■     one object instead of two

The mask is now cut into objects at the image's resolution, where the model drew the line, and then the object ids are shrunk onto the voxels. Two objects stay two ids even where no black voxel survives between them. The fix is in both the server and the browser.

Who sees a change

  • Server models whose input voxels are finer than their output: touching objects are no longer merged. A thin neck of one object that the shrink skips leaves pieces sharing one id, since they're one object at the image's resolution.
  • Everyone else, including every browser-catalog model: the image's pixels are the same size as the voxels or coarser, and the labels are exactly what they were. The same voxels are written in every case; only the ids can differ, and only when shrinking.

Code

  • pipeline.mask_to_write_shape now returns the mask's objects on the write box (int32, 0 for background), numbered in the box's raster order. labels_for_box takes those objects instead of a bool mask.
  • The staged mask_write.npy holds the objects. The preview's overlay and the mask fraction read them as > 0.
  • The browser's maskToWriteShape and labelsForBox mirror the Python. The parity fixture gains a case with a one-pixel line on coarser voxels.

Checks

  • New test: two objects split by a one-pixel line, at 4 nm pixels on 8 nm voxels, come out as two ids. It failed before the fix.
  • tests/ai_annotate: 278 passed. The full vitest suite passes and the typecheck is clean.
  • The old and new pipelines gave identical labels on 108 random cases where the image is the voxels' size or coarser, with painted voxels and the planes beside.

…an its voxels

Accept shrank the model's mask to the write box's voxels by nearest
neighbour and only then cut it into 4-connected objects. On voxels
coarser than the image's pixels (8 nm pixels into 16 nm voxels) each
voxel takes every second pixel, so the one-pixel black line the prompt
asks for between touching objects could fall between the pixels taken,
and the two objects were written as one id.

mask_to_write_shape now labels the mask at the image's resolution and
shrinks the object ids, numbered again in the box's raster order;
labels_for_box takes those objects instead of a bool mask. The staging
keeps the objects, and the preview's overlay and the mask fraction read
them as > 0. The browser's maskToWriteShape and labelsForBox do the
same, and the parity fixture gains a one-pixel line on coarser voxels.
Where the image is as fine as the voxels or coarser, as for every
browser-catalog model, the labels are what they were.
@davidackerman
davidackerman merged commit 888085c into main Oct 19, 2026
7 checks passed
davidackerman added a commit that referenced this pull request Oct 19, 2026
@davidackerman
davidackerman deleted the ai-annotate-split-before-shrink branch October 19, 2026 14:40
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.

1 participant