Repository navigation
Recover thickness on poorly resolved bilayers (cubic sampling, full-radius averaging, opt-in forced prior) - #64
Merged
Merged
Conversation
At ~1 nm/px an OMM's leaflets merge into one flat-topped peak. The whole-surface average is then unresolved too, so no global prior was formed, the recovery tier never ran, and most triangles came back NaN. - Opt-in thickness_measurements.force_bilayer_prior (default false; --force_bilayer_prior): fit the unresolved surface average as a bilayer anyway, accepted only if it passes the recovery checks, and use it as the prior. New per-triangle forced_bilayer_prior column (1/0) flags thicknesses that rely on it; with bilayer_resolution it identifies low-confidence regions. Prints a hint when the average is unresolved. - density_sampling.interpolation: cubic (new default) or linear. Linear interpolation's extra between-voxel blur merges leaflets at ~1 nm/px. Spline coefficients are computed only for the block the surface spans; out-of-volume samples stay NaN. Used by sample_density and refine_mesh. - radius_neighbors replaces the k=500 nearest queries in measure_thickness and refine_mesh, which silently shrank the averaging radius on dense meshes (~8 nm instead of 12). It thins the surface uniformly (fixed seed) so neighborhoods span the full radius. On four 9.98 A/px OMM surfaces: strict measured fraction 14-49% -> 25-58%, and 81-87% with the forced prior. A 4-iteration refine_mesh on the example OMM matches main (vertices differ by a 0.02-0.03 nm median). CHANGELOG entries are folded into 2.0.0b6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bbarad
added a commit
that referenced
this pull request
Oct 7, 2026
Conflicts resolved to keep both features: - measure_thickness: process_single_surface, run_measure_thickness and the CLI take both force_bilayer_prior (#64) and the split-half noise options. The split-half fits reuse the same global prior, and radius_neighbors' padding (index n, distance inf) matches what split_half_neighbors expects. - docs/quantifications.md (moved on this branch): picks up the updated bilayer_resolution text and the new forced_bilayer_prior column. Real-data split-half results on the example OMM/IMM are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a high-quality 9.98 Å/px OMM dataset,
measure_thicknessreturned NaN for 47–84% of triangles even after refinement. At ~1 nm/px the two leaflets merge into one flat-topped peak. The whole-surface average is unresolved too, so_global_bilayer_priorreturnedNone, and the recovery tier for merged triangles never ran. Every failure was "single peak, no prior". The tomograms are not low-passed (power extends to Nyquist).Changes
thickness_measurements.force_bilayer_prior(defaultfalse; CLI--force_bilayer_prior/--no_force_bilayer_prior). Fits an unresolved surface average as a bilayer anyway, accepted only if it passes the recovery checks (R², separation clear of the floor, comparable leaflet amplitudes), and uses it as the prior.forced_bilayer_priorcolumn (1/0): 1 where the thickness relies on the forced prior.thicknessstays a single column; together withbilayer_resolution, the flag identifies low-confidence regions. The column is always written so the schema is stable.measure_thicknessnow prints a message suggesting a less-binned tomogram, or the new option.density_sampling.interpolation: cubic(new default) orlinear, used bysample_densityandrefine_mesh. Linear interpolation's between-voxel blur is enough to merge leaflets at ~1 nm/px. Spline coefficients are computed only for the block the surface spans, and out-of-volume samples stay NaN.radius_neighborsreplaces the fivecKDTree.query(k=500)neighbor queries inmeasure_thicknessandrefine_mesh. On dense meshes those silently shrank the averaging radius (~8 nm instead of 12 nm; ~16 nm instead of 20 nm on the example OMM). The surface is now thinned uniformly with a fixed seed so neighborhoods span the full radius at the same cost.fit_triangle_chunknow returns(thickness, offset, resolution, recovered).Results
Four 9.98 Å/px OMM surfaces, 1,500 sampled triangles each, 12 nm radius:
force_bilayer_prior(flagged)interpolation: linearreproduces earlier sampling.Refinement check
A 4-iteration
refine_meshon the example OMM was run withmain, with this branch using linear sampling, and with this branch using cubic sampling. All three produce nearly identical surfaces:The neighbor cap was hit for 92% of triangles at 20 nm. It barely matters for centering here, likely because offsets are smoothed again over 25 nm with an uncapped query.
Tests
119 pass:
test_forced_bilayer_prior.py,test_cubic_sampling.py,test_radius_neighbors.py;test_thickness_worker.pyfor the 4-tuple.sample_density+measure_thicknesswere also run end to end on the example tomogram, both with and without the forced prior (the latter on deliberately blurred profiles).Release
The CHANGELOG entries are folded into the 2.0.0b6 section (version stays
2.0.0b6), so the beta can be re-cut frommainafter merge.🤖 Generated with Claude Code