Skip to content

Fix shader problem that broke 2-D vertex data - #715

Merged
kroq-gar78 merged 4 commits into
mainfrom
claude/issue-714-irf5g0
Sep 21, 2026
Merged

kroq-gar78 merged 4 commits into
mainfrom
claude/issue-714-irf5g0

Conversation

@alexhuth

Copy link
Copy Markdown
Contributor

This pr fixes the recently introduced shader bug that broke 2-D vertex data displays (see issue #714). The core issue is that we are hitting the upper limit on the number of vertex attributes that we can pass to the shaders, and this causes threejs to silently choke. It also adds some tests that look for blank rendered images. This is not a real visual regression test — that would supersede this test.

@alexhuth
alexhuth requested a review from kroq-gar78 August 23, 2026 19:27
@kroq-gar78

Copy link
Copy Markdown
Contributor

This fixes Vertex2D in Chromium. I was hoping it would also fix it in Firefox (#375), since the root cause was the same from my investigation, but apparently not.

@kroq-gar78

kroq-gar78 commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

I also don't like that this test duplicates a chunk of the shader code (mainly definitions). I think we should test for general OpenGL errors in one of the basic rendering tests, or even cortex.export.save_3d_views at runtime.

@kroq-gar78 kroq-gar78 linked an issue Aug 31, 2026 that may be closed by this pull request
Nothing caught gh-714: the shader that failed to link compiled fine, no
javascript error was raised, and the screenshot was written -- it was
just empty.

test_webgl_shaders links every combination of options the viewer builds
surface, pick and depth shaders with, through THREE.ShaderMaterial (the
same path DataView.getShader() uses) in a real SwiftShader GL context.
Needs Chromium but no subject database, so it runs in a couple of
seconds and covers variants no render test puts on screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wiUP7TLgDqSfnv4n9ySZj
@kroq-gar78 kroq-gar78 self-assigned this Sep 11, 2026
claude and others added 3 commits September 17, 2026 14:34
The surface shaders sit right up against the 16 vertex attribute slots
WebGL guarantees, and #679 added two more of them (flatheight and
flatBumpNorms) to the vertex data shader so that bumpy_flatmap would work
for Vertex dataviews. 2D vertex data needs four data attributes rather
than two, which pushed surface_vertex to 17: it still compiles, but it
fails to link with "Too many attributes", and three.js only reports that
on the browser console, so the viewer just draws a black screen. The same
overflow hits plain Vertex data as soon as equivolume sampling is on.

Rather than giving these values slots of their own, pack them into the
spare components of attributes that are already bound:

- the white matter and pial vertex areas, which only the equivolume depth
  sampling reads, move into auxdat.zw (auxdat.x is the medial wall mask
  and .y the curvature; z and w were left at zero by brainctm)
- the flatmap bump height moves into the fourth component of the bumped
  flatmap normals, so the two become a single vec4 flatbump attribute

Every shader variant now links: the worst case, 2D vertex data on a
subject with a flatmap, goes from 19 attributes to 16. Renders of the
paths that did work before -- volume data with equivolume sampling, and
the bumpy flatmap -- are pixel-identical to what they were.

Fixes gh-714.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wiUP7TLgDqSfnv4n9ySZj
Regenerated the visual-regression references with the same pinned
chromium/playwright/matplotlib as the rest of the reference set; no
existing reference image changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Drop the dead compiled/attributes/max_attributes diagnostics from the
  assert: a compile failure always fails the subsequent link too (its
  error text already lands in the joined log).
- Add test to ensure we catch compile failures.
@kroq-gar78
kroq-gar78 force-pushed the claude/issue-714-irf5g0 branch from f534196 to 8a03351 Compare September 21, 2026 08:21
@kroq-gar78
kroq-gar78 merged commit 434aaa7 into main Sep 21, 2026
14 checks passed
mvdoc pushed a commit that referenced this pull request Sep 21, 2026
#715 landed the same fix on main, and did it better. Both pack the bumpy
flatmap's height into the fourth component of its normals, but #715 also
moves the white matter and pial vertex areas into the two unused components
of `auxdat`, which brings the worst case (2D vertex data on a subject with
a flatmap) from 19 attributes down to 16 rather than to 17.

That extra headroom matters: at 17, plain `Vertex` data still overflows as
soon as equivolume sampling is on, which our fix left broken. #715 also
names the packed attribute `flatbump`, factors its declaration into
`utils.flatbump_attr`, and adds test_webgl_shaders.py, which links every
shader variant the viewer can build and would have caught gh-714 in the
first place.

Dropping our version of the JS here so the merge of main takes #715's
wholesale rather than resolving two equivalent edits line by line. The
xfail removals and the reference-image bookkeeping are reconciled in the
merge commit that follows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yNgwu9miNKAuv79yPMqU8
mvdoc pushed a commit that referenced this pull request Sep 21, 2026
main gained #715, which fixes gh-714 the same way this branch had just
tried to and then some, so the JS took main's side wholesale (see the
revert commit before this one). The two conflicts were both in the
bookkeeping that fix came with:

- test_visual_regression.py: the comment explaining why Vertex2D was
  xfailed. Both sides dropped the xfail; main's side wins, since #715 is
  what fixed it, not this branch.
- reference_images/README.md: same, plus provenance. Took main's account of
  the four Vertex2D images and kept this branch's note that the flatmap
  references still need regenerating for the NaN/alpha changes.

Left this branch's correction to the webgl/quickflat reference counts
(18/14, not 16/12): #715 added four images without updating that line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yNgwu9miNKAuv79yPMqU8
mvdoc pushed a commit that referenced this pull request Sep 21, 2026
Three gaps this branch's own changes left uncovered.

test_webgl_shaders.py: #715's matrix already covers this branch's extra
`nanmask` attribute -- all 27 variants still link, worst case included --
but not the options this branch adds to the volume-sampling shader.
_nan_alpha_variants adds 20 surface_pixel variants over dataalpha x nanmean
x layers (1 and 32) x the cmap/2d/rgb branches, hasflat and equivolume
pinned on. 47 variants, still under seven seconds, and no subject database
needed.

test_visual_regression.py, the alpha map on 2D dataviews: _dataview grew a
`twod_alpha` switch and NAN_ALPHA_DATAVIEW_NAMES now lists all four classes
that take an explicit `alpha=`, not just the two RGB ones. Volume2D and
Vertex2D accepted the argument before this branch as well, but it went into
`attrs` as a bare ndarray, so quickflat mishandled it and the viewer failed
to load the dataview -- that is the first fix in this PR and nothing in the
visual suite could see it. _build_nan_dataview passes it too, which finally
makes that builder do what its own comment says: NaN every place the rule
names, the alpha map included.

test_visual_regression.py, averaging across depth: a new
multilayer_nan_dataviews suite, the three volumetric classes at both values
of `nanmean`, with quickflat's `thick` and the viewer's `layers` both set
to 32 and NaNs in diagonal slabs two voxels thick, so most surface points
have both NaN and valid samples under them. The other suites NaN whole
columns and leave depth sampling alone -- at their defaults the renderers
do not even sample the same number of depths (quickflat 32, the viewer 1)
-- so none of them can see the change this PR makes to how a column of
samples is combined. Measured on renders from the regenerate path, the
scenario separates cleanly and the renderers agree: data covers 0.797 of
the flatmap with nanmean on and 0.196 with it off in quickflat, 0.815 and
0.185 in the viewer.

The Volume2D case of that suite is flaky, and not because of anything
here. Setting `layers` above 1 intermittently leaves the viewer's RPC proxy
answering `{}` to every later query, so save_3d_views dies on the next
parameter it sets; it never recovers and the browser reports no error.
Building the same dataview inline and running the same sequence reproduces
it on main at about the same rate, roughly one run in four, so it is a
pre-existing viewer/JSProxy bug rather than a gh-695 regression -- this is
just the first test to drive `layers` above 1 on a 2D dataview. Documented
in the test and left in.

References for all of this still have to be generated; see
reference_images/README.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yNgwu9miNKAuv79yPMqU8
AgarwalNilay pushed a commit that referenced this pull request Sep 22, 2026
The bumpy flatmap gives a flatmap relief that survives full data coverage.
It was computed in javascript, at viewer load time, and it was noisy enough
that the result was treated with iterated Laplacian smoothing.

Reading the shipped code, two things stood out. The height was
`computeFlatVolumeHeight(wmareas, vertexvolumes)` -- the denominator was the
*folded* white matter vertex area, not the flattened one -- so with
`A_pial = r**2 * A_wm` it collapsed to `thickness * (1 + r + r**2) / 3` and
never looked at the flatmap at all. The version that does use flat areas was
commented out, because it spikes: `V / A_flat` has a denominator that goes to
zero wherever flattening crushed a triangle, and smoothing a field of ratios
afterwards is dominated by exactly those outliers.

Replace it with a physical model, computed in python and cached.
cortex.polyutils.FlatSlab treats the tissue between the white matter and pial
surfaces as a soft elastic solid, pins the white matter side to the flatmap and
lets the pial side settle, minimising a stable Neo-Hookean energy (Smith, De
Goes & Kim 2018) over wedge elements cut into tetrahedra. What keeps real
tissue from spiking is not gravity -- over a 3mm slab `rho*g*t/mu` is about
0.026, a couple of percent -- but shear: a tall narrow column is expensive
because it shears against its neighbours and relieves that by spreading
sideways. So the pial vertices get three degrees of freedom rather than one,
gyral crowns end up wider at the top than at the bottom and sulcal fundi
narrower, and the relief comes out smooth without anything being smoothed.

With no body force and a uniform material the shear modulus factors out of the
minimiser entirely, so Poisson's ratio is the only material parameter that
affects the result and it is the only one the API takes.

On S1's left hemisphere this drops the height's p99.9-to-median ratio from 2.4
(naive) and 3.8 (the old javascript) to 1.9, with the tallest point at 5.9mm
rather than 13.9mm, and the height correlates with mean curvature at +0.52.

Also move the equivolume depth sampling's vertex areas out of javascript, which
lets them use the cotangent-weighted operator in Surface.smooth rather than a
uniform umbrella, and removes the last of the per-load numeric kernels.

Plumbing:

- surfinfo.bumpy_flatmap and surfinfo.equivolume_areas cache the results.
  Both store more than one value per vertex, so they avoid the `left`/`right`
  keys that get_surfinfo turns into a Vertex by concatenation.
- get_surfinfo takes `generate=False` for info too expensive to compute behind
  someone's back. The bumpy flatmap uses it: it is generated when a flatmap is
  imported, and a subject imported before this existed gets a flat flatmap and
  a message saying how to fill it in, rather than a viewer that disappears for
  a quarter of an hour.
- import_flat generates it, and now also clears the surface info derived from
  the flatmap it just replaced -- clear_cache only empties cache/, so
  distortion and flat_border could go stale there too.
- The offsets ride to the viewer in a `flatoffset` ctm attribute. The surface
  shaders use all 16 of the vertex attribute slots webgl guarantees (see #715),
  so javascript unpacks them into `flatbump.w` and the unused `w` of `wm` and
  of the flat morph target rather than adding a seventeenth.
- bumpy_flatmap_scale exaggerates the relief for display without touching the
  cached geometry.
- The ctm cache token goes to v4, since older packs have neither attribute.

Tests check the relaxation against cases with known answers rather than against
stored output: an identity flat map has to leave the slab exactly alone, a
uniformly stretched thin slab has to reproduce the homogeneous solution in its
interior, and the requested Poisson ratio has to come back out of the small
strain elasticity tensor. A headless test renders the flatmap with and without
the relief and fails if the two images match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JG9XE4ZAitPgsQYRDwtwVT
kroq-gar78 added a commit that referenced this pull request Sep 30, 2026
…views (#695)

* WebGL viewer: make opacity slider and `o` toggle work for Vertex data (#684)

The dat.GUI opacity slider and the `o` shortcut both drive the `dataAlpha`
uniform. The volume shaders apply it (`vColor *= dataAlpha`), but the
`surface_vertex` fragment shader only declared it and never read it, so
opacity was a silent no-op for Vertex, Vertex2D and VertexRGB data.

- shaderlib.js: scale the per-vertex data colour by `dataAlpha` before
  compositing over the curvature. `vColor` is a varying, so scale into a
  local `dColor`. Scaling all four channels keeps the premultiplied-alpha
  convention of the volume path, so opacity 0 shows curvature only.
- mriview_surface.js: `toggleOpacity` now remembers the last visible
  opacity instead of rounding it (0.7 -> 0 -> 0.7, not 0.7 -> 0 -> 1),
  and no longer leaks an implicit global.
- test_webgl_headless.py: regression test rendering a saturated Vertex at
  opacity 1 -> 0 -> 1 and counting coloured pixels. Fails on main
  (53,760 red pixels at opacity 0) and passes with the fix.

Closes #684

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8oA4mUvTLd7sbs9iFaqM8

* test: replace magic-number thresholds in vertex opacity test with named, image-relative quantities

Address review on #685: the 1000 / 50 / 1000 pixel-count thresholds were
unexplained. The assertions now use named constants derived from the
measured renders and related to the image size or to each other:

- opacity 1: red-dominant pixels must cover >= 10% of the frame
  (measured ~27% at this view; curvature-only renders give 0%).
- opacity 0: at most 1% of the opacity-1 count (measured 0).
- opacity restored to 1: within 5% of the original count, which is a
  stronger check of the round trip than the previous "> 1000".

Re-verified: fails against the pre-fix shader (53,760 red pixels remain
at opacity 0), passes with the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8oA4mUvTLd7sbs9iFaqM8

* FIX unify NaN and alpha handling across quickflat, WebGL and RGB dataviews

Rule: a NaN anywhere at a voxel/vertex (data, either 2D dimension, any RGB
channel, or the alpha map) renders fully transparent in both renderers;
otherwise the alpha (2D colormap, alpha= kwarg, RGB alpha channel) is honored.

- Volume2D/Vertex2D: alpha= is a real attribute (not an ndarray in attrs,
  which crashed the WebGL viewer with a 500); multiplied into the colormap
  alpha in quickflat for both classes, shipped to WebGL as a normalized float
  brain and applied in the shaders (folded into the nanmask attribute for
  vertex data: 2D vertex views already use 15 of the 16 guaranteed vertex
  attributes and adding more made the program fail to link silently).
  Saved/restored in HDF.
- VolumeRGB/VertexRGB: NaN mask is applied without writing into the
  temporary returned by .volume for masked (linear) alpha Volumes; single-
  frame alpha is broadcast against multi-frame NaN masks (closes #629);
  NaN inside the alpha map -> 0 explicitly; color_voxels no longer mutates
  the caller's alpha array.
- quickflat: RGBA is averaged across thickness in premultiplied space (no
  dark halos around transparent voxels, matching the WebGL compositor);
  make_svg no longer indexes arr[..., 3] on 2-D scalar images.
- Dataview2D.to_json keeps vmin/vmax of exactly 0.
- Package deduplicates byte-identical brains (Vertex2D(x, x) / VertexRGB(r, r, r)
  used to be reordered twice and crash the viewer).
- WebGL hover/click readout shows NaN for masked vertices instead of 0.
- Tests: browser-free NaN/alpha unit tests, quickflat-vs-WebGL parity tests,
  and dataset-switching tests checking that NaN masks and alpha never leak
  between datasets (setData and addData).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdjyZMFXsn6mRMJu9i8ige

* ENH nanmean across layers in the WebGL viewer; quickflat nanmean=True by default

The WebGL multi-layer sampling summed the layer samples, so one NaN voxel at
any depth made the whole fragment transparent (quickflat's nanmean=False).
surface_pixel now averages only the valid (non-NaN) layer samples and is
transparent only when none is valid, matching quickflat's nanmean=True. A
`nanmean` toggle in the surface controls (on by default) restores the old
any-NaN-is-transparent behavior, and quickflat's make_figure /
make_flatmap_image / add_data default to nanmean=True so both renderers agree.
NaN-free data renders identically either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdjyZMFXsn6mRMJu9i8ige

* DOC describe the vmin/vmax defaults of Volume2D/Vertex2D

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdjyZMFXsn6mRMJu9i8ige

* FIX address review: nanmean for RGBA paths, movie masks, uint8 alpha, package dedup

- quickflat: nanmean now applies to dataviews that reach the RGBA branch.
  2D/raw conversions carry their NaN mask, so nanmean=True averages the
  valid voxels only and nanmean=False hides any pixel touched by a NaN
  voxel; native RGB (NaN already alpha 0) treats fully transparent voxels
  as missing, like the WebGL RGB textures.
- WebGL surface_pixel: NANMEAN also applies to RGB textures (fully
  transparent layer samples are skipped; validity from the current frame
  since an unbound next-frame sampler reads as opaque black).
- WebGL vertex data: the nanmask combines the masks of both frames being
  blended (next frame only while framemix > 0), so a NaN in the next frame
  no longer interpolates towards a fake 0.
- _mask_alpha: uint8 alpha maps are filled with byte 0 (their inferred
  vmin is a percentile of the bytes, 255 for a constant map).
- Package: same-name brains are deduplicated only when their class,
  subject, transform, shape, dtype and mask match; otherwise raise a
  clear ValueError instead of serving one brain with another's metadata.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdjyZMFXsn6mRMJu9i8ige

* FIX gh-714: pack the flat bump attributes so the 2D vertex shader links

#679 added two vertex attributes to the surface shaders, `flatBumpNorms`
(vec3) and `flatheight` (float), for the bumpy flatmap. That put the 2D
vertex program one over MAX_VERTEX_ATTRIBS, so it failed to link:

    THREE.WebGLProgram: gl.getProgramInfoLog() Too many attributes (mixNorms1)
    THREE.WebGLProgram: Could not initialise shader.
    WebGL: INVALID_OPERATION: drawElements: no valid shader program in use

Vertex2D flatmaps then rendered blank and `save_3d_views` raised, which is
gh-714. This branch had already spent the last free slot on `nanmask`, and
folds the per-vertex alpha map into it for the same reason.

Both of #679's attributes are static per-vertex geometry, computed together
in one block of mriview_surface.js, so they pack into a single vec4 (.xyz
the bump normal, .w the bump height) with no behavioural change and no
per-dataset buffer churn. That returns one slot, which is all that was
needed. `auxdat` has two free components but is deliberately left alone:
it is static geometry, and per-dataset state there would mean rewriting the
buffer on every setData.

Verified on the headless viewer: a Vertex2D flatmap went from a link
failure plus ~140 `no valid shader program in use` errors to none, and the
three `vertex2d_*` cases in test_webgl_nan_alpha_parity.py plus the two
Vertex2D cases in test_webgl_switching.py pass again. Those five passed
before this branch merged main and regressed on the merge.

The strict xfail #672 put on Vertex2D is removed: it was marked
`raises=RuntimeError, strict=True` precisely so that a render which starts
working reports rather than passing silently. Its reference images still
have to be generated, along with a regeneration of the whole stored set for
this branch's NaN/alpha changes; reference_images/README.md now says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yNgwu9miNKAuv79yPMqU8

* Revert our gh-714 shader fix in favour of #715

#715 landed the same fix on main, and did it better. Both pack the bumpy
flatmap's height into the fourth component of its normals, but #715 also
moves the white matter and pial vertex areas into the two unused components
of `auxdat`, which brings the worst case (2D vertex data on a subject with
a flatmap) from 19 attributes down to 16 rather than to 17.

That extra headroom matters: at 17, plain `Vertex` data still overflows as
soon as equivolume sampling is on, which our fix left broken. #715 also
names the packed attribute `flatbump`, factors its declaration into
`utils.flatbump_attr`, and adds test_webgl_shaders.py, which links every
shader variant the viewer can build and would have caught gh-714 in the
first place.

Dropping our version of the JS here so the merge of main takes #715's
wholesale rather than resolving two equivalent edits line by line. The
xfail removals and the reference-image bookkeeping are reconciled in the
merge commit that follows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yNgwu9miNKAuv79yPMqU8

* TST cover the 2D alpha map and across-depth NaN averaging

Three gaps this branch's own changes left uncovered.

test_webgl_shaders.py: #715's matrix already covers this branch's extra
`nanmask` attribute -- all 27 variants still link, worst case included --
but not the options this branch adds to the volume-sampling shader.
_nan_alpha_variants adds 20 surface_pixel variants over dataalpha x nanmean
x layers (1 and 32) x the cmap/2d/rgb branches, hasflat and equivolume
pinned on. 47 variants, still under seven seconds, and no subject database
needed.

test_visual_regression.py, the alpha map on 2D dataviews: _dataview grew a
`twod_alpha` switch and NAN_ALPHA_DATAVIEW_NAMES now lists all four classes
that take an explicit `alpha=`, not just the two RGB ones. Volume2D and
Vertex2D accepted the argument before this branch as well, but it went into
`attrs` as a bare ndarray, so quickflat mishandled it and the viewer failed
to load the dataview -- that is the first fix in this PR and nothing in the
visual suite could see it. _build_nan_dataview passes it too, which finally
makes that builder do what its own comment says: NaN every place the rule
names, the alpha map included.

test_visual_regression.py, averaging across depth: a new
multilayer_nan_dataviews suite, the three volumetric classes at both values
of `nanmean`, with quickflat's `thick` and the viewer's `layers` both set
to 32 and NaNs in diagonal slabs two voxels thick, so most surface points
have both NaN and valid samples under them. The other suites NaN whole
columns and leave depth sampling alone -- at their defaults the renderers
do not even sample the same number of depths (quickflat 32, the viewer 1)
-- so none of them can see the change this PR makes to how a column of
samples is combined. Measured on renders from the regenerate path, the
scenario separates cleanly and the renderers agree: data covers 0.797 of
the flatmap with nanmean on and 0.196 with it off in quickflat, 0.815 and
0.185 in the viewer.

The Volume2D case of that suite is flaky, and not because of anything
here. Setting `layers` above 1 intermittently leaves the viewer's RPC proxy
answering `{}` to every later query, so save_3d_views dies on the next
parameter it sets; it never recovers and the browser reports no error.
Building the same dataview inline and running the same sequence reproduces
it on main at about the same rate, roughly one run in four, so it is a
pre-existing viewer/JSProxy bug rather than a gh-695 regression -- this is
just the first test to drive `layers` above 1 on a 2D dataview. Documented
in the test and left in.

References for all of this still have to be generated; see
reference_images/README.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yNgwu9miNKAuv79yPMqU8

* TST regenerate the visual-regression reference images

236e8c9 added the multilayer and nan_alpha 2D cases but none of their
reference images, so those suites could not run: _render_and_check_dataview
fails outright on a missing reference. Adds the 16 that were absent (the 12
multilayer_nan_dataviews/ renders, and the four nan_alpha_dataviews/ ones for
Volume2D and Vertex2D) and refreshes the 18 that had drifted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5AMBzUzPKfDfVdjLvxqri

* TST xfail the multilayer suite's cross-renderer leg (gh-749)

quickflat and the WebGL viewer average over the same number of depth samples
here, but not at the same depths: quickflat takes the interior fractions
linspace(0, 1, thick+2)[1:-1] while the shader takes i/(layers-1), which
reaches both the white-matter and pial surfaces. On smooth data that is
invisible -- every cross check outside this suite passes at mean|diff| 1.3-1.9
of 255 -- but the multilayer suite's NaN slabs are two voxels thick, at the
sampling limit, so the offset flips which samples are NaN and the two renders
disagree pixel by pixel. Five of its six parameter sets breach the tolerance.

The disagreement is phase noise rather than a difference in what is drawn:
signed bias within +-2, transparency agreeing to the same 1.19% outline as the
passing suites, and mean|diff| dropping to 1.3 under a sigma=4 blur against a
1.18 floor. Both renderers also respond to nanmean the same way, which is what
the suite exists to cover.

Aligning the two grids would shift every render and invalidate both reference
sets, so that is left to gh-749. Only the cross-renderer leg is conceded: the
reference legs stay strict, and are asserted before the xfail so a real
regression cannot hide behind it. Uses the imperative pytest.xfail rather than
a mark, so the sixth parameter set -- which stays inside the tolerance --
simply passes, and all six will go green on their own once gh-749 is fixed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5AMBzUzPKfDfVdjLvxqri

* TST stop the nan suite's vertex masks from covering the whole surface

_build_nan_dataview NaN'd the vertex channels over three disjoint index ranges
-- idx >= total/2, idx < total/4, and the quarter between -- which tile the
surface exactly. A NaN in any channel renders an element transparent, so with
no vertex left clean the two classes drawing on all three (Vertex2D, and
VertexRGB through red/green/alpha) came out as bare curvature: 0% of their
opaque pixels carried any color, against 10-100% everywhere else. The four
nan_dataviews references for them pinned an empty flatmap, so they covered
nothing -- a regression in 2D or RGB vertex NaN handling would still render
blank and still pass, and the cross-renderer leg compared one blank flatmap
against another.

The volume regions never had this problem because they are three overlapping
halves on independent axes (x>=50, y>=50, z>=15), whose union is 7/8 and leaves
37500 clean voxels. Splits the vertex regions on the vertex coordinates the
same way, at the median of each axis so the masks stay at half the surface
exactly. That leaves 39784 vertices (13.1%) clean, against 12.1% of the volume,
and Vertex2D and VertexRGB now retain 13.07% all-finite elements rather than
none.

Volume regions are untouched, so only the four nan_dataviews vertex references
need regenerating.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5AMBzUzPKfDfVdjLvxqri

* TST put the nan suite's clean remainder where the data is visible, and guard it

Two follow-ups to the vertex masks.

Leaving a remainder is not sufficient on its own: it also has to land somewhere
the data can be seen. Vertex2D's visibility is governed by the alpha its 2D
colormap derives from dim2, which is the accuracy bump, and >= on all three
axes put the survivors off it -- mean accuracy 0.23, 3.5% of them above 0.5,
and a render with 1.2% of its opaque pixels colored against Volume2D's 9.9%.
Flipping the z comparison moves the remainder onto the bump (mean accuracy
0.62, 70% above 0.5) for the same 12% of the surface. VertexRGB improves with
it; Vertex is unaffected, since it reads only the primary mask.

Adds test_nan_builders_leave_clean_elements, asserting on the dataviews
themselves that each NaN suite leaves at least 5% of elements free of NaNs in
every channel -- the suites sit at 12% and above, so the floor is not tuned.
Nothing could catch the original fault, because a blank render matches a blank
reference exactly and agrees with the other renderer's blank render exactly as
well; the failure is only visible before rendering. Checked against the masks
as they were: it fails on Vertex2D and VertexRGB at 0.00% and passes the rest.

The check needs no browser, so the playwright skip moves off pytestmark and
onto the five render tests individually. Without playwright this file used to
collect 39 skips and assert nothing; it now runs the 13 builder checks and
skips the 26 renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5AMBzUzPKfDfVdjLvxqri

* Update test images

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGP6ytTVeCU29FbmuLNBza

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Aditya Vaidya <kroq-gar78@users.noreply.github.com>
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.

Vertex2D objects do not render in webviewer

3 participants