Skip to content

Support segment lists in multi-annotation upload#9360

Open
fm3 wants to merge 13 commits intomasterfrom
multi-upload-segment-lists
Open

Support segment lists in multi-annotation upload#9360
fm3 wants to merge 13 commits intomasterfrom
multi-upload-segment-lists

Conversation

@fm3
Copy link
Copy Markdown
Member

@fm3 fm3 commented Mar 5, 2026

  • Add segment list when uploading multiple volume annotations at once
    • Implementation detail: uses three routes: (1) initializing step, where thinned-out merged tracing is stored. Then (2) the volume data is merged, the resulting MergedVolumeStats (with id maps) stored in a new temporary store for 1hour, and then (3) the tracing objects are merged again, this time with all lists, using the id maps.
  • »Create a new tree group for each file«
    • now also applies to segment lists. I don’t think we need to change the wording.
    • Drops the .zip/.nml suffix for the wrapping group names
    • Skips wrapping tree groups for annotations with no trees/treeGroups
    • Skips wrapping segment groups for annotations with no segments/segmentGroups

URL of deployed dev instance (used for testing):

Steps to test:

  • Upload multiple volume annotations with brushed volume data
  • Volume data should show, segment list and groups should be ok
  • If »Create a new tree group for each file« was selected, the groups should be properly wrapped
  • Also try multi-NML upload, should still work
  • Also try multi-zip (hybrid) with no volume brushed, should still work, should still have volume mags, be brushable afterwards
  • Also try with proofreading annotations

Issues:


  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py)
  • Removed dev-only changes like prints and application.conf edits
  • Considered common edge cases

@fm3 fm3 self-assigned this Mar 5, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8c1b6846-408d-427e-b0a6-9ecec288da51

📥 Commits

Reviewing files that changed from the base of the PR and between 20ebf6d and e8ed808.

📒 Files selected for processing (1)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala
🚧 Files skipped from review as they are similar to previous changes (1)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala

📝 Walkthrough

Walkthrough

Implements a staged merge for volume annotations: add initializeForMerge to persist emptied placeholder stats, store temporary MergedVolumeStats, then finalize merge in mergedFromContents. Adjust MergedVolumeStats shape, wire TemporaryMergedVolumeStatsStore, and preserve NML-derived tracing names.

Changes

Cohort / File(s) Summary
NML upload & upload flow
app/models/annotation/AnnotationUploadService.scala, app/models/annotation/nml/NmlResults.scala
Preserve empty tracings by guarding grouping; use NmlParseSuccess.fileNameWithoutExtension when rebuilding parse results to keep expected tracing names. Minor single-expression refactors.
WK client merge orchestration
app/models/annotation/WKRemoteTracingStoreClient.scala
Split remote merge into staged calls: initializeForMerge with thinned VolumeTracings, then initial data upload, then mergedFromContents. Add thinOutVolumeTracings helper.
Tracing-store controller & routes
webknossos-tracingstore/app/.../VolumeTracingController.scala, webknossos-tracingstore/conf/tracingstore.latest.routes
Add initializeForMerge endpoint/route; initialDataMultiple stores temporary merged stats; mergedFromContents pops stored stats to finalize merge. Inject TemporaryMergedVolumeStatsStore.
Temporary storage & DI
webknossos-tracingstore/app/.../tracings/TemporaryTracingStore.scala, webknossos-tracingstore/app/.../tracingstore/TracingStoreModule.scala
Rename TemporaryVolumeDataStoreTemporaryMergedVolumeStatsStore; change generic value type to MergedVolumeStats; bind as eager singleton.
Merged volume model & merge logic
webknossos-tracingstore/app/.../tracings/volume/MergedVolume.scala, webknossos-tracingstore/app/.../tracings/volume/VolumeTracingService.scala, webknossos-tracingstore/app/.../tracings/volume/VolumeSegmentIndexService.scala
Reshape MergedVolumeStats (largestSegmentId→Option, add seenMags, remove sortedMagsList), add magsMergedWith, change empty signature, adjust MergedVolume logic and visibility, change initializeWithDataMultiple to return MergedVolumeStats, and update propagation into merged VolumeTracing. Simplify segment-index check.
Docs / changelog
unreleased_changes/9360.md
Document that merged volume annotations from multiple uploads now include populated segment lists.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

refactoring

Suggested reviewers

  • MichaelBuessemeyer
  • normanrz

Poem

🐇 I thinned the traces, hid a seed,

I stored its stats for later need,
Then popped them back to finish the art —
Segments awake where once were parts. 🎶

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Support segment lists in multi-annotation upload' clearly summarizes the main change—adding segment list merging support for multi-annotation uploads—and is specific and concise.
Description check ✅ Passed The description provides clear implementation details about the three-step flow, explains the segment grouping behavior changes, includes testing instructions, and references linked issues. It is directly related to the changeset.
Linked Issues check ✅ Passed The PR fully implements the objective from issue #6887 by adding a three-step merge flow that temporarily stores MergedVolumeStats to enable segment list remapping during multi-annotation uploads, addressing the core requirement.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to support segment list merging in multi-annotation uploads and related functionality (grouping, file naming, merge flow). No extraneous changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch multi-upload-segment-lists
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@fm3 fm3 changed the title WIP Support segment lists in multi-annotation upload Support segment lists in multi-annotation upload Mar 5, 2026
@fm3 fm3 marked this pull request as ready for review March 5, 2026 13:31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/models/annotation/WKRemoteTracingStoreClient.scala (1)

210-225: Consider error handling for partial failures in the three-step merge.

The three-step workflow (initializeForMerge → initialDataMultiple → mergedFromContents) has no explicit cleanup if an intermediate step fails. While the 1-hour expiry on temporaryMergedVolumeStatsStore provides eventual cleanup, a failure after initializeForMerge but before mergedFromContents leaves an orphaned tracing at version 0 with empty segments.

This may be acceptable given the rarity of this scenario and the eventual expiry, but consider whether explicit cleanup on failure would improve consistency.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala`:
- Around line 151-157: The stats method is passing
presentMags.map(vec3IntToProto) (Set[Vec3IntProto]) into
MergedVolumeStats.seenMags which expects Set[Vec3Int]; fix by passing the
original presentMags (no vec3IntToProto conversion) or alternatively change the
MergedVolumeStats.seenMags type to Set[Vec3IntProto] if the rest of the code
expects protos—update the stats method (symbol: stats) to use presentMags
directly or adjust the MergedVolumeStats definition accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22d3fb80-6de4-4a8b-9d47-effc52f23ffa

📥 Commits

Reviewing files that changed from the base of the PR and between 32e2042 and e034bfc.

📒 Files selected for processing (11)
  • app/models/annotation/AnnotationUploadService.scala
  • app/models/annotation/WKRemoteTracingStoreClient.scala
  • app/models/annotation/nml/NmlResults.scala
  • unreleased_changes/9360.md
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/TracingStoreModule.scala
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/TemporaryTracingStore.scala
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeSegmentIndexService.scala
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala
  • webknossos-tracingstore/conf/tracingstore.latest.routes

@fm3 fm3 requested a review from MichaelBuessemeyer March 5, 2026 13:48
Copy link
Copy Markdown
Contributor

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

The code looks good, but I am a little unsure whether I can correctly follow the code changes.

So basically, the code was already able to merge multiple volume tracings and their data into one and also remap the segment ids via the mergedVolumeStats.idMaps, but it wasn't used before: The previous upload procedure did not do the 3rd additional step after merging the volume stats, and thus mergedVolumeStats.idMaps was disregarded and never really used, althougt the information was technically already present to calculate the correct segment stats list? So this PRs main changes are: Adding a 3rd step to use the ``mergedVolumeStats.idMaps` to be able to calculate the correct segment list? Or am I missing something 🤔

I'll do the testing tomorrow

@fm3
Copy link
Copy Markdown
Member Author

fm3 commented Mar 9, 2026

Yes, that’s basically right. The MergedVolumeStats are a summary of the volume data merging process. It contains a map indicating the remapped ids. This remapping needs to be applied to the segment list as well. Since before this PR, the merging of volume data was the last step, and the tracing objects were merged first, the MergedVolumeStats were unavailable at that point (a mocked empty one was passed instead). Now, with the final step, all data is available and can be used to populate the protos.

Copy link
Copy Markdown
Contributor

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

Ok here is what I did for testing till now:

  • I created 4 annotations on the same datasets.

  • Each got a new empty volume annotation layer

  • In each I selected 2 or 3 segment ids and drew with them a little. There were cell id duplicated between the annotations as well as unique onces per annotation-

  • Now I merged the 2nd into the 1st annotation via the menu in the navbar. In the modal I entered the annotation id. The merging succeeded, however: The data of the new volume layers without a fallback layer were all merged into the layer with the fallback data:

    • annotation 1: segmentation + fallback & Volume
    • annotation 2: segmentation + fallback & Volume
    • merged annotation: segmentation + fallback (includes the annotations from the "Volume" layers)
  • I think this is a rather unwanted behaviour imo.

  • I did this with all 4 annotations pairwise so I ended up with a fully merged annotation

  • => The segment list was correct 🎉 , but in the "wrong" layer.

  • When I tried the merging via nml / zip upload (I downloaded annotation 2 as zarr zip) the upload failed with the 2nd error message being "Could not merge volume annotations, as their magnifications differ. Please ensure each annotation has the same set of mags."

  • Then I tried to upload the wkw downloaded zip and this worked, but the result was incorrect imo and also did not add new segments and no separate group the the not added new segments was created. Not sure what went wrong there.

  • A second try also showed problem with the zip + wkw merge upload: The segment list was unchanged in the state before merging and only brush stroke from the annotation that was merged into was missing in the merged anntotation. Thus, I had the segment list entries from the 1st annotation, but not its data and the data from annotation 2 but not their segments :/.

Sorry, that this is so complicated and that I seem to find buggy behaviour although you already tested this. Maybe I am doing something wrong during testen? But, I just followed the workflow I'd say a user would come up with on their own 🤔

@fm3
Copy link
Copy Markdown
Member Author

fm3 commented Mar 16, 2026

Thanks for testing so thoroughly! I think there is a little misunderstanding going on. I think the problems you described, though I did not understand each one fully, are related to merging annotations where each one has multiple volume layers. This case is indeed not yet properly supported, but that is not what this PR is about. I now wrote #9399 to track this case.

This PR, however, is about drag’n’dropping multiple annotation zips (where each has just one volume layer) into the dashboard, creating a new annotation.

So to test this, please create multiple annotations (where each has just one volume layer. Either all with fallback or none with fallback), download them, and then upload those zips together. Of course some of the described bugs could be happening there too, but I would assume they are independent.

What you also tested was the import-volume-data case (Dragging an annotation zip into an existing annotation). This was also not touched in this PR. Looks like there is also a bug there. So I wrote #9401 to track that.

I confirmed that both bugs are already present on master, so reviewing this PR can be independent of them.

Sorry that my steps to take weren’t clearer! But it’s good to draw attention to these (somewhat unrelated) bugs too, of course :-)

@MichaelBuessemeyer MichaelBuessemeyer self-requested a review March 18, 2026 13:46
Copy link
Copy Markdown
Contributor

@MichaelBuessemeyer MichaelBuessemeyer left a comment

Choose a reason for hiding this comment

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

Awesome works 🎉

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/models/annotation/WKRemoteTracingStoreClient.scala`:
- Around line 211-225: The RPC calls to initializeForMerge and
mergedFromContents use the default timeout while initialDataMultiple uses
.withLongTimeout, causing potential timeouts on large merges; update the two rpc
invocations that call "${tracingStore.url}/tracings/volume/initializeForMerge"
and "${tracingStore.url}/tracings/volume/mergedFromContents" to also use
.withLongTimeout (same timeout behavior as the existing initialDataMultiple
call) so all three heavy merge-phase requests use a long timeout.

In
`@webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala`:
- Around line 211-217: The code currently calls
temporaryMergedVolumeStatsStore.pop(newTracingId) which destructively removes
the only MergedVolumeStats before calling volumeTracingService.merge and
saveVolume; change this to a non-destructive read (e.g.,
temporaryMergedVolumeStatsStore.get/read/peek) to obtain mergedVolumeStats, then
perform volumeTracingService.merge(tracingsFlat, mergedVolumeStats, ...) and
volumeTracingService.saveVolume(newTracingId, mergedTracing.version,
mergedTracing); only after saveVolume succeeds, delete the staged stats (call
pop/delete) for newTracingId, or if you prefer keep pop but wrap merge+save in a
try/finally that reinserts the mergedVolumeStats on failure so
mergedFromContents can be retried. Ensure you update references to
temporaryMergedVolumeStatsStore.pop(newTracingId) and the error path that
expects "mergedVolumeStats.notFound".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9cf24277-566c-446b-b392-48bbc7d0de7a

📥 Commits

Reviewing files that changed from the base of the PR and between e034bfc and 20ebf6d.

📒 Files selected for processing (3)
  • app/models/annotation/WKRemoteTracingStoreClient.scala
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala
🚧 Files skipped from review as they are similar to previous changes (1)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ImportVolumeData seems broken? Segment lists for Merged Volume Annotations created from multiple zips in upload

2 participants