Skip to content

Conversation

MichaelBuessemeyer
Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer commented Sep 19, 2025

This PR fixes the Frontend mesh visibility setting function of the API. Some PR in the past added an additional nesting level to the meshes structure to support additional coordinates. This was not adjusted in the frontend mesh visiblity api function. The function itself has a check for whether the mesh is present but was missing this additional nesting level. This PR fixes this. The correct nesting structure is shown in: https://github.com/scalableminds/webknossos/blob/master/frontend/javascripts/viewer/model/reducers/annotation_reducer.ts#L420-L432

While working on this, I thought it would be nice to be able to script something that does some additional followup actions once the mesh is loaded. But the mesh loading function does not return a promise which resolves when the mesh is loaded. Therefore to add generality, I added a utility function to the frontend api that enables us to wait for a specific action. E.g. the mesh loading has a specific "finished" action which could be used in the case I described. Please feel free to tell me what you think about this little addition :)

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • View a DS with a mesh file
  • Select the mesh file and its corresponding agglomerate view if needed
  • Locate a segment with its ID
  • Insert the mesh id and position in the following script and run it.
window.webknossos.apiReady(3).then(async (api) => {
  const segmentIdsAndPositions = [
      [<mesh_id>, [<mesh_pos>]],
  ];
  const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
  for (const [segmentId, position] of segmentIdsAndPositions) {
    api.data.loadPrecomputedMesh(segmentId, position);
    api.data.setMeshVisibility(segmentId, false);
  }
  await sleep(5*1000);
  for (const [segmentId, position] of segmentIdsAndPositions) {
     api.data.setMeshVisibility(segmentId, true);
  }
 await sleep(5*1000);
  for (const [segmentId, position] of segmentIdsAndPositions) {
     api.data.removeMesh(segmentId);
  }
 await sleep(5*1000);
  for (const [segmentId, position] of segmentIdsAndPositions) {
     api.data.loadPrecomputedMesh(segmentId, position);
  }
await sleep(5*1000);
  for (const [segmentId, position] of segmentIdsAndPositions) {
     api.data.resetMeshes();
  }
});
  • This should not crash (see console). Watch the 3d viewport to observe correctness.

    1. Load mesh invisible
    2. make visible again
    3. remove mesh
    4. load mesh again (visible)
    5. remove all meshes
  • Optional: Test on master -> shouldn't work -> the setMeshVisibility part should print an error in the console.

Issues:


(Please delete unneeded items, merge only when none are left open)

  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py)

…ibility api

- Also add api to wait for a specific action, making async script easier implementable
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

📝 Walkthrough

Walkthrough

DataApi and UtilsApi were updated to use per-coordinate mesh maps keyed by additional coordinates; mesh operations (setMeshVisibility, removeMesh, resetMeshes) use a cached state and per-coordinate lookups. Segment mesh controller applies visibility when meshes are newly added. An event-bus wait-for-action utility was exposed.

Changes

Cohort / File(s) Summary
API updates (DataApi & UtilsApi)
frontend/javascripts/viewer/api/api_latest.ts
- Imported getAdditionalCoordinatesAsString and eventBus.
- DataApi.setMeshVisibility caches state, computes coordKey, looks up mesh in per-coordinate map meshes[coordKey][segmentId], and dispatches visibility only if mesh exists for that coord; throws more specific errors otherwise.
- DataApi.removeMesh and DataApi.resetMeshes use cached state and per-coordinate mesh checks.
- Exposed waitForAction(actionType): Promise<any> via eventBus on UtilsApi.
Controller changes (segment mesh behavior)
frontend/javascripts/viewer/controller/segment_mesh_controller.ts
- Cache state = Store.getState() for reuse.
- When a mesh is newly added, derive isVisible from state.localSegmentationData for that layer/mesh/segment (default true) and call setMeshVisibility(segmentId, isVisible, layerName, additionalCoordinates).
- Use cached state for getActiveSegmentationTracing(state).
Flycam accessor export
frontend/javascripts/viewer/model/accessors/flycam_accessor
- Exported getAdditionalCoordinatesAsString used to derive per-coordinate keys for mesh maps.
Release notes / docs
unreleased_changes/8936.md
- Documented new frontend action-wait utility and fixes to mesh APIs; noted that mesh visibility is applied during mesh loading.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • philippotto
  • daniel-wer
  • normanrz

Poem

I hop through coords with whiskers bright,
Keys braided from floats and pixels tight.
Per-coordinate maps, I scent each seam,
The event bus hums — I wait and dream.
A tiny hop, visibility set right 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately references the fixes to the frontend mesh API functions and the handling of visibility changes during mesh loading. However, it is verbose, contains a repeated word, and uses awkward phrasing that may reduce clarity.
Description Check ✅ Passed The pull request description clearly outlines the existing issue, the adjustments to mesh visibility functions, the added utility for awaiting actions, and includes detailed testing steps and context, making it directly relevant to the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-mesh-visibility-FE-api

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7abc08 and 62b850d.

📒 Files selected for processing (1)
  • frontend/javascripts/viewer/api/api_latest.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/viewer/api/api_latest.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push

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.

@MichaelBuessemeyer
Copy link
Contributor Author

@philippotto This is a small fix PR. Might be something easy. But feel free to re-assign

@MichaelBuessemeyer MichaelBuessemeyer changed the title Fix checking for mesh presence for Frontend mesh visibility api function Fix multiple Frontend API mesh mesh related functions and apply mesh visibility changed done while still loading the mesh Sep 19, 2025
Copy link
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)
frontend/javascripts/viewer/controller/segment_mesh_controller.ts (1)

258-264: Mesh visibility state verified; minor initialization inconsistency

  • Verified: WebknossosState.localSegmentationData.meshes is a Record<string, Record<number, MeshInformation> | undefined> and MeshInformation includes isVisible; UPDATE_MESH_VISIBILITY writes to localSegmentationData[layerName].meshes[additionalCoordKey][id].isVisible and segment_mesh_controller reads that same path. (store.ts, annotation_reducer.ts, segment_mesh_controller.ts)
  • Issue: maybeAddAdditionalCoordinatesToMeshState initializes meshes[additionalCoordKey] with [] (an array) instead of {} (an object). This works at runtime but diverges from the declared Record<number, MeshInformation> shape — change $set: [] → $set: {} for consistency and clarity. (frontend/javascripts/viewer/model/reducers/annotation_reducer.ts)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0408f1 and a92cf3e.

📒 Files selected for processing (3)
  • frontend/javascripts/viewer/api/api_latest.ts (6 hunks)
  • frontend/javascripts/viewer/controller/segment_mesh_controller.ts (1 hunks)
  • unreleased_changes/8936.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/viewer/api/api_latest.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: knollengewaechs
PR: scalableminds/webknossos#8492
File: frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts:671-674
Timestamp: 2025-05-15T19:44:16.110Z
Learning: Visibility updates for user bounding boxes are intentionally handled separately from other property updates, with dedicated update actions for visibility changes.
🧬 Code graph analysis (1)
frontend/javascripts/viewer/controller/segment_mesh_controller.ts (1)
frontend/javascripts/viewer/model/accessors/volumetracing_accessor.ts (1)
  • getActiveSegmentationTracing (342-342)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (2)
unreleased_changes/8936.md (1)

1-6: LGTM!

The changelog entry clearly documents both the added functionality and the bug fixes. The description properly covers the addition of the action-waiting function and the fixes to multiple mesh-related API functions.

frontend/javascripts/viewer/controller/segment_mesh_controller.ts (1)

266-266: Good optimization with cached state usage.

Using the cached state from line 258 instead of calling Store.getState() again is a good performance optimization and ensures consistency across the function execution.

@philippotto
Copy link
Member

Thanks for the fix! It's a bit unfortunate that TS did not complain about the incorrect access to the nested structure. Ideally, this should be guarded better, but that's out of scope for this PR probably.

While working on this, I thought it would be nice to be able to script something that does some additional followup actions once the mesh is loaded. But the mesh loading function does not return a promise which resolves when the mesh is loaded. Therefore to add generality, I added a utility function to the frontend api that enables us to wait for a specific action. E.g. the mesh loading has a specific "finished" action which could be used in the case I described. Please feel free to tell me what you think about this little addition :)

Cool idea! However, I'm not sure whether this is the right direction to tackle the problem. For example, if a script loads several meshes in parallel, there is still no way to wait for a particular mesh to complete, right? Also, api users would need to know about the dispatched action types and also would need to rely on that (even though, it might be subject to change). Therefore, I would remove the utility function again and re-think the problem once there is a priority for it. Do you agree?

Copy link
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: 0

🧹 Nitpick comments (3)
frontend/javascripts/viewer/api/api_latest.ts (3)

153-153: Remove unused import (eventBus).

Leftover from an earlier approach; it’s not referenced.

-import { eventBus } from "viewer/model/helpers/event_bus";

2608-2626: Per-coordinate removeMesh check is correct; align error message with context.

Include layer and coord key for clarity.

-      throw new Error(
-        `Mesh for segment ${segmentId} was not found in State.localSegmentationData.`,
-      );
+      throw new Error(
+        `Mesh for segment ${segmentId} not found for layer "${effectiveLayerName}" and additionalCoordinates "${additionalCoordKey}".`,
+      );

2636-2646: Use cached state consistently; consider clarifying scope in docs.

  • Use the local state variable when reading meshes to avoid mixing snapshots.
  • Optional: Mention that resetMeshes operates on the current additionalCoordinates only.
-    const segmentIds = Object.keys(
-      Store.getState().localSegmentationData[effectiveLayerName]?.meshes?.[additionalCoordKey] ||
-        EMPTY_OBJECT,
-    );
+    const segmentIds = Object.keys(
+      state.localSegmentationData[effectiveLayerName]?.meshes?.[additionalCoordKey] ||
+        EMPTY_OBJECT,
+    );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a92cf3e and a7abc08.

📒 Files selected for processing (2)
  • frontend/javascripts/viewer/api/api_latest.ts (5 hunks)
  • frontend/javascripts/viewer/controller/segment_mesh_controller.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/viewer/controller/segment_mesh_controller.ts
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/javascripts/viewer/api/api_latest.ts (3)
frontend/javascripts/viewer/model/accessors/volumetracing_accessor.ts (1)
  • getRequestedOrVisibleSegmentationLayerEnforced (366-380)
frontend/javascripts/viewer/model/accessors/flycam_accessor.ts (1)
  • getAdditionalCoordinatesAsString (288-298)
frontend/javascripts/viewer/constants.ts (1)
  • EMPTY_OBJECT (392-392)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (2)
frontend/javascripts/viewer/api/api_latest.ts (2)

60-60: Importing getAdditionalCoordinatesAsString is correct.

Matches the new meshes map keying strategy.


2580-2597: Include layer and coordinate key in error message

-      throw new Error(
-        `Mesh for segment ${segmentId} was not found in State.localSegmentationData.`,
-      );
+      throw new Error(
+        `Mesh for segment ${segmentId} not found for layer "${effectiveLayerName}" and additionalCoordinates "${additionalCoordKey}".`,
+      );

Verified that all mesh maps use getAdditionalCoordinatesAsString (with the default ";" separator) for keying.

@MichaelBuessemeyer
Copy link
Contributor Author

Cool idea! However, I'm not sure whether this is the right direction to tackle the problem. For example, if a script loads several meshes in parallel, there is still no way to wait for a particular mesh to complete, right? Also, api users would need to know about the dispatched action types and also would need to rely on that (even though, it might be subject to change). Therefore, I would remove the utility function again and re-think the problem once there is a priority for it. Do you agree?

Yeah, also felt a bit clunky for me. It might be helpful in some rare cases and only seems like a monkeypatch to be honest 👍

Should be removed now

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.

2 participants