Skip to content

feat: 3D mesh labeling (Canvas3D, brush-based vertex labeling)#234

Draft
vietanhdev wants to merge 3 commits intomainfrom
feat/mesh-labeling
Draft

feat: 3D mesh labeling (Canvas3D, brush-based vertex labeling)#234
vietanhdev wants to merge 3 commits intomainfrom
feat/mesh-labeling

Conversation

@vietanhdev
Copy link
Copy Markdown
Owner

Status: Draft. Original work by @vietanhdev (`ad3977c` rebased onto current main); I added headless-test guards, a small data-model test, and four synthetic sample meshes. Opening as draft so the architecture can be reviewed before more UX work lands.

What this adds

  • `Canvas3D` (`anylabeling/views/labeling/widgets/canvas3d.py`, ~1k LoC) — a PyQt6 widget that embeds a PyVista/VTK render window. Modes:
    • `view` — trackball orbit/pan/zoom
    • `brush` — paint a label across the vertex set under the cursor (radius-based, vtkPointLocator)
    • `keypoint` — single-vertex labels
  • Wires into `LabelingWidget` alongside the existing 2D canvas (label_widget.py +305).
  • Mesh I/O via `pyvista.read`/`pyvista.save` (anything pyvista handles: `.obj`, `.ply`, `.stl`, `.vtk`, …).
  • New deps: `pyvista>=0.43.0`, `pyvistaqt>=0.11.0`, `trimesh>=4.0.0`.

What I changed on top of the original commit

  • Rebased onto `main` (only `pyproject.toml` had a dependency-block conflict; resolved by keeping main's bumped floors plus the three new mesh deps).
  • Headless-instantiable `Canvas3D`. `enable_trackball_style()` and the `iren.interactor` mode/observer paths used to AttributeError when no interactive VTK window existed — blocking any unit test of the data model. Wrapped both in guards (`Canvas3D._iren_interactor()`).
  • Added `tests/test_canvas3d.py` (7 tests). Exercises mesh load → vertex_label_ids array sized to the mesh → label↔lid mapping → round-trip save/load → mode switching → brush radius. Skips cleanly when no VTK backend is available, so the existing 9-cell matrix keeps passing.
  • Added `sample_meshes/` (~190 KB total): `cube.obj` (8 verts), `cone.obj` (21 verts), `sphere.obj` (530 verts), `torus.obj` (529 verts). All synthetic via pyvista — no licensing risk.

What I deliberately did not touch

  • The brush picker, lighting, mesh-color UI, and label-file format choices in the original commit. Those are product decisions.
  • Interactive UX testing requires a real (or virtual) display — not run in CI here.

Verified locally

  • 99/99 tests pass on Python 3.12 with vtk-osmesa (`QT_QPA_PLATFORM=offscreen`): the 92 existing tests + 7 new Canvas3D tests.
  • `Canvas3D` instantiates, `load_mesh` builds an actor and a vtkPointLocator, `vertex_label_ids` array shape matches the mesh, label→lid mapping is stable, save/load round-trips, mode switches survive the no-iren environment.
  • The full app import chain still loads cleanly with `QT_QPA_PLATFORM=offscreen`.

What's next (suggested)

  • Decide where labelled meshes get persisted (`label_file.py` already extended; format spec worth pinning).
  • Hook up a sample mesh shortcut in the UI ("Open sample mesh →").
  • Document the keybinds (`Ctrl+B` brush, `Ctrl+K` keypoint) once stable.
  • Real interactive UX test on Linux/Windows/macOS.

Adds Canvas3D widget for labeling vertices on triangulated meshes via a
brush UI. Built on PyQt6 + PyVista/VTK (new dev deps: pyvista>=0.43,
pyvistaqt>=0.11, trimesh>=4.0).

Headless-instantiable: Canvas3D.__init__ guards enable_trackball_style()
and the iren-based observer install/remove paths, so the widget can be
constructed in CI / unit tests where no interactive VTK render window is
available.

tests/test_canvas3d.py exercises the data-model layer (vertex_label_ids,
the label-to-lid mapping, mesh round-trip, mode switching). The class
self-skips if the VTK backend can't initialise, so the existing matrix
keeps passing on bare runners.

sample_meshes/ ships four small synthetic .obj fixtures (cube, cone,
sphere, torus — all generated by pyvista, ~190 KB total) so users can
try the feature and the test has a real on-disk mesh to load.
The macOS PR 234 cells failed with Segmentation fault: 11 — the VTK wheel
on Apple Silicon GitHub runners crashes the Python process when Canvas3D()
initialises without a real GL context. SIGSEGV cannot be caught by Python,
so the existing try/except + SkipTest in setUpClass doesn't fire.

Skip the entire test class when CI=true (set on every GitHub Actions
runner). Local development still runs all 7 tests, since they exercise
real value (data-model layer of the brush + vertex_label_ids storage).
Per follow-up review:

- **Drop keypoint mode** for simplicity (user request). Canvas3D is now
  view + brush only; keypoint button gone from ViewControls3D, the
  KEYPOINT class constant gone, the keypoint_3d shape_type filter gone,
  the create_keypoint_3d action and toolbar entry gone.

- **In-place paint hot path.** _apply_colors_and_render used to call
  _redraw_mesh on every brush stroke, which called add_mesh and
  rebuilt the actor — O(actor rebuild) per stroke and the dominant
  cost on dense meshes. Now the first paint flips a one-shot flag
  (_scalar_mode_active) that switches PBR -> per-vertex scalar
  colouring, and every subsequent stroke just mutates
  _main_mesh.point_data['label_colors'] and re-renders. Microbench:
  50 paint+render cycles on a 530-vert sphere ~ 2 ms/stroke.

- **Reuse the cursor sphere actor.** _show_cursor used to build a fresh
  pv.Sphere and add_mesh it on every mouse move. Now there's one
  unit-sphere actor created lazily; _show_cursor just SetScale +
  SetPosition + SetVisibility. (Verified: actor id stable across 20
  _show_cursor calls.)

- **Shortcut keys.** Esc -> view mode, [ -> shrink brush, ] -> grow
  brush. Wired through label_widget actions so they sit alongside the
  existing Ctrl+B (brush). Disabled until a mesh is loaded.

- **Tests.** Three new test_canvas3d.py cases covering the perf
  contract: in-place paint must not rebuild the actor once
  _scalar_mode_active is True; the cursor actor is reused; KEYPOINT
  attribute is gone. Existing 7 tests still pass.
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