Skip to content

The aligner is dead. Long live the aligner. - #739

Open
candytaco wants to merge 7 commits into
mainfrom
claude/awesome-wright-5bz9m3
Open

candytaco wants to merge 7 commits into
mainfrom
claude/awesome-wright-5bz9m3

Conversation

@candytaco

@candytaco candytaco commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

I had claude re-implement the mayavi aligner in webgl.

cortex.align.webgl_maual(<surface>, <transform>) gives a webgl viewer that can edit transforms.

Updated to show the data also on the surface in real-time as you move the transform.

Controls:
WASD or RMB to translate, QE to rotate, hold shift to move in 1/10th size increments

Aligner_.TZfs.aligner-test.-.Vivaldi.2026-09-19.13-28-07.webm

Port of the mayavi aligner to the WebGL viewer: the functional reference
volume stays on its own voxel grid, the pial and white matter surfaces
are moved into its space by rotations and translations only, and they
are cut off at the displayed slices so that their outline shows on each
slice.

- cortex/webgl/aligner.py: tornado server (reference mosaic, CTM pack,
  page, save endpoint), the world frame (voxel grid in mm permuted to
  RAS) and the JSAligner handle with tagged calls and frame waits
- resources/js/aligner.js, aligner.html, resources/css/aligner.css:
  coronal, axial, sagittal and 3D views, cursor and slice navigation,
  mesh translation and rotation by mouse and keyboard with undo,
  colormap with range, brightness, contrast, gamma and flip, mesh color
  and opacity, a mode painting the volume onto the surface, saving
- shaderlib.js: aligner_volume and aligner_mesh shader builders
- menu.js: color picker controls
- cortex.align.webgl_manual, [webgl_aligner] config defaults, docs and
  tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115CeZYMxr1wvRSL2V8meKZ
The browser aligner refused to open a transform that had cached masks.
It now opens, and deletes those masks when it saves: they were cut out
of the reference volume through the alignment being replaced, nothing
else invalidates them, and db.save_xfm refuses to write over a transform
that still has them.

- cortex/webgl/aligner.py: cached_masks and clear_masks, called from the
  save handler before db.save_xfm; the save response names what it
  deleted, and the page config lists the masks
- the page warns, on opening a transform that has masks, that saving
  deletes them and that data masked with them has to be masked again
- view_only stays an explicit choice rather than something masks force

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115CeZYMxr1wvRSL2V8meKZ
The browser aligner replaces it, and it had not worked since mayavi
stopped running on anything newer than Ubuntu 14.04.

- drop cortex/mayavi_aligner.py, cortex.align.mayavi_manual and the
  [mayavi_aligner] config section
- drop the mayavi sections of docs/align.rst and their screenshots, and
  point docs/transforms.rst at the browser aligner
- the mayavi imports elsewhere (segmentation, surface display in
  freesurfer and database) are untouched, as are the mypy overrides they
  need

Also make the aligner tests redirect the transform's mask paths into a
directory of their own, so they neither depend on nor delete the masks
another test file leaves in the bundled filestore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115CeZYMxr1wvRSL2V8meKZ
- WASD moves the mesh in the view under the mouse, like the arrow keys,
  in both cases with shift for the fine step
- the colormap dropdown draws a strip of each colormap beside its name,
  through select2, the library the viewer's colormap picker uses; a css
  block in the shared template lets the aligner's stylesheet load last,
  since the viewer hides the select2 control it now needs
- a transform field above the save button names what the alignment is
  saved under, so it can be saved as a new transform, leaving the one it
  was loaded from and its masks alone; the name is checked before it
  reaches the filesystem
- an asterisk on the save button and in the title marks an alignment
  that differs from the one last saved
- cortex.align.webgl_manual spells out every option rather than taking
  **kwargs

menu.js keeps the controller of a button action, so its label can change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115CeZYMxr1wvRSL2V8meKZ
A `layout` control chooses where the surface is shown. "4 panels" is the
layout as it was. "panels + surface" keeps the three slices showing their
planes and outlines and turns the corner the 3D view occupies into the
viewer, so the mesh can be nudged in the slices while the data on the
surface follows. "surface" gives that viewer the whole window, framed on
the surface the way the viewer opens on one.

Both carry the reference data through the alignment as it stands, saved
or not, and redraw as the mesh moves, so an alignment can be judged from
the pattern the data makes on the cortex before it is committed.

Also corrects the documented mouse buttons of the 3D view: its controls
are the viewer's, which pan on the middle button and zoom on the right.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115CeZYMxr1wvRSL2V8meKZ
- replace the `view` and `layout` dropdowns with one `display` control:
  3 ortho + 3D slices, 3 ortho + 3D brain, or the data on the surface
- draw the data view's surfaces in the anatomy's own frame with the morph
  targets of the CTM pack, so they inflate and flatten there while the
  alignment only moves where the volume is sampled
- add `unfold` and `pivot`, and cull the medial wall while the flatmap shows
- take the keyboard back from the panel when the mouse enters a view, and
  start with a view under the keys
- keep the open colormap list within the window, which was scrolling the
  page sideways when it reached past the right edge

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115CeZYMxr1wvRSL2V8meKZ
@candytaco
candytaco marked this pull request as ready for review September 19, 2026 20:59
Copilot AI lite review requested due to automatic review settings September 19, 2026 20:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The save path has unresolved authorization, data-loss, completion, and rendering edge cases.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 3 Medium severity

Open (4)

Comment thread cortex/webgl/aligner.py
Comment thread cortex/webgl/aligner.py Outdated
Comment thread cortex/webgl/aligner.py Outdated
Comment thread cortex/webgl/resources/js/shaderlib.js Outdated
@mvdoc

mvdoc commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

@claude review

@claude

claude Bot commented Sep 19, 2026

Copy link
Copy Markdown

Claude finished @mvdoc's task in 1m 52s —— View job


Review of #739 — the WebGL aligner

  • Gather context (PR diff, files, prior Copilot review comments)
  • Review cortex/webgl/aligner.py (new tornado server + save endpoint)
  • Review cortex/align.py changes (webgl_manual entry point)
  • Review cortex/webgl/resources/js/aligner.js and shaderlib.js changes
  • Review tests in cortex/tests/test_webgl_aligner.py
  • Post final review

This is a substantial, well-documented replacement of the mayavi aligner. The docstrings are thorough and the test suite (cortex/tests/test_webgl_aligner.py, 748 lines) is unusually complete, including headless-browser Playwright coverage. I independently verified the four issues Copilot's automated review already flagged — all four are real, confirmed against the current code, not artifacts of a stale diff:

1. /save has no auth/CSRF protection (cortex/webgl/aligner.py:511-539)
SaveHandler.post performs a destructive filesystem write (db.save_xfm) and deletion (clear_masks) with no token or Tornado XSRF check. WebApp (cortex/webgl/serve.py:334) binds via bind_sockets(port) with no address restriction, so this is reachable from any host that can route to the port, not just loopback. This is a shared risk with cortex.webgl.show, but that server is read-only — this is the first place the webgl server exposes a destructive write, so it's worth tightening (bind to 127.0.0.1 and/or require the per-session token that's already generated for RPC calls).

2. save() returns before the save actually completes (aligner.py:288-290, resources/js/aligner.js:998-1024)
JSAligner.save()'s docstring says it saves "like the Save button," but under the hood Aligner.prototype.save in JS fires an async $.ajax POST and returns the literal string "saving" immediately — success/failure is only reported later via showStatus(), which the Python side never sees. Confirmed by the test itself: test_aligner_in_headless_browser (test_webgl_aligner.py:506-511) has to separately poll recorder.wait(1) after calling handle.save(), rather than trusting the return value — i.e., the test works around the gap instead of exercising a contract that holds. A caller who calls .save() and then tears down the server immediately can lose the write silently.

3. get_client() / get_local_client() don't attach server (aligner.py:544-550)
The open_browser=True path manually does object.__setattr__(client, "server", server) (aligner.py:569) after calling get_client(), but get_client()/get_local_client() themselves don't do this. So the documented open_browser=False flow (server = webgl_manual(...); client = server.get_client()) produces a JSAligner whose _call sees server is None and silently falls back to WebApp.send's ~2s timeout instead of the intended 120s tagged-reply loop (aligner.py:200-232). This is masked in every current test by manually patching it in (e.g. test_webgl_aligner.py:455: object.__setattr__(handle, "server", server)), which means the "normal" documented path is untested. Worth setting self as server inside get_client/get_local_client directly.

4. Unguarded vmin == vmax division in aligner_volume shader (resources/js/shaderlib.js:1015)
float norm = (value - vmin) / (vmax - vmin); has no zero-denominator guard. show() in aligner.py:422-425 guards the initial value, but the colormap min/max sliders in the UI can still be moved to equal values interactively, producing NaN/Inf sampling. Note this mirrors an existing pattern elsewhere in shaderlib.js (e.g. lines 20-21), so it's not a new class of bug introduced by this PR, but it's worth a max(vmax - vmin, epsilon) guard in the new shader since it's easy to trigger from the aligner's own UI.

Everything else — the coordinate-frame math (reference_frame, world/coord conventions), mask invalidation on save, the transform-name validation (XFM_NAME regex blocking path traversal), and the CTM/mesh handling — looked correct and carefully commented. No functional issues found there.

No code changes were made — this was a review-only request, per the instructions ("review" → provide feedback, don't implement).
(branch: claude/awesome-wright-5bz9m3)

- WebApp binds the loopback interface unless a caller asks otherwise; the
  viewer opens up to every interface only when domain_name names one
- the aligner's save endpoint takes only posts carrying the token of the
  page it served, so nothing else reaching the port can write a transform
- JSAligner.save waits for the save to land and returns the server's
  message, raising when it was refused
- get_client hands the handle its server, which the documented
  open_browser=False flow needs for the tagged-reply loop
- guard a zero vmin/vmax span in the aligner shader
- drop get_local_client, which called an attribute WebApp does not have
- add an edit history to the panel: a row per edit since load, with how far
  it moved and turned the surfaces; clicking one puts that alignment back
- confirm closing the page while the alignment differs from the saved one

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115CeZYMxr1wvRSL2V8meKZ
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.

4 participants