Conversation
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.
This PR is an updated redesign of the earlier prototype (
OneSurfaceFewVoxelsHeadModel) for the new release with its currentcedalion.dotlayout. For reference, see the old PR#38.Summary
Adds a new
VoxelHeadModelclass alongsideTwoSurfaceHeadModelso DOT/fNIRS image reconstruction can target a reduced set of brain voxels directly, preserving depth information that surface-based reconstruction discards. The class mirrors the public API ofTwoSurfaceHeadModel, so it drops intoForwardModel,ImageRecon, and the rest of thecedalion.dotpipeline without changes to consumers.Motivation
TwoSurfaceHeadModelprojects voxel-space fluence onto the cortical mesh, restricting reconstruction to surface vertices and discarding depth.What's new
Class
cedalion.dot.VoxelHeadModel(src/cedalion/dot/voxel_head_model.py) — a dataclass with the same shape asTwoSurfaceHeadModelexcept:brainis acdc.Voxelscloud rather than acdc.Surfacemesh.brain_mask: np.ndarray(3D bool over the segmentation grid) lets reducers be applied repeatedly while preserving the link to the original grid.voxel_to_voxel_brainproperty alias is provided for user code that prefers the more honest name (see Naming below).Standard atlases
dot.get_standard_headmodel(model, kind="surface" | "voxel")—kind="voxel"builds aVoxelHeadModelfrom the same Colin27 / ICBM152 segmentation files used by the surface variant. Defaultkind="surface"preserves the existing call signature.Reducers (three layered, each returns a new instance)
How to reduce all brain voxels to those that are needed in the end?
reduce_voxels_to_probe(geo3d, max_dist)— drop voxels far from any optode. Pre-MCX, geometric.reduce_voxels_by_fluence(fluence_fname, rel_threshold)— NeuroDOT-style mask (Make_Good_Vox.m:41). Per-voxelΣ_optodes |fluence|(max over wavelengths); drop voxels belowrel_threshold × max. Runs between MCX andcompute_sensitivityso the sensitivity matrix is small from the start.reduce_voxels_to_sensitivity(Adot, sensitivity_threshold)— post-Adot absolute-threshold cleanup.Helpers
cedalion.dot.utils.reduce_and_map_brain_voxels(...)— porting of the old utility into the currentdotlayout, withcdt.QLength-typedmax_dist.cedalion.dot.head_model.align_and_snap_to_scalp(...)andsnap_to_scalp_voxels(...)— extracted to module-level so both head-model classes reuse the same implementation.Backwards compatibility
TwoSurfaceHeadModelis unchanged behaviourally; itsalign_and_snap_to_scalp/snap_to_scalp_voxelsmethods now delegate to the new module-level helpers.ForwardModel.compute_sensitivityis unchanged. The duck-typed attribute names (brain.nvertices,voxel_to_vertex_brain,voxel_to_vertex_scalp) match both head models.ImageReconand theGaussianSpatialBasisFunctionsfamily operate on point clouds via KDTree (no mesh connectivity / geodesic distance), so they work oncdc.Voxels.verticesunchanged. One defensive guard was added atimage_recon.py:820(getattr(brain, "vertex_coords", {})) so the parcel-aware SBF branch does not crash when the brain has novertex_coordsattribute. Surface behaviour is unchanged —Surfacealways exposes that attribute, so thegetattrreturns the same dict the old code accessed directly.get_standard_headmodeladds an optionalkindkwarg with a back-compatible default.Implementation notes
voxel_to_vertex_brainand Adot'svertexaxis still indexes the kept voxels. This is a deliberate misnomer: keeping the names letscompute_sensitivity,compute_stacked_sensitivity, plotting, and BIDS I/O all work without forks. The class docstring defines "vertex" here as "the generalized row index of the brain target representation". Avoxel_to_voxel_brainproperty alias gives user code a clearer name. A future refactor that renames the dim everywhere (e.g.target) is a larger separate PR.from_surfacesconstructor. AVoxelHeadModelhas no brain mesh to consume. The only realistic external-mesh use case (re-using an atlas's precomputed scalp mesh) is covered by an optionalscalp_surface_filekwarg onfrom_segmentation.get_brain_mni152_coordsraisesNotImplementedErrorfor now; voxels do not carry per-vertex MNI coords. Users can callhead.brain.apply_transform(t_ijk2mni)to get MNI positions.Test plan
python -m pytest tests/test_dot_voxel_head_model.py -v— 8 new tests cover smoke build, save/load round-trip, apply_transform round-trip, all three reducers, thekind="voxel"atlas loader, and the alias property.python -m pytest tests/test_dot_forward_model.py tests/test_dot_image_recon.py -v— 74 existing dot tests continue to pass (regression check on the surface path).ruff check src/cedalion/dot/ tests/test_dot_voxel_head_model.py— clean.Example jupter notebook
examples/head_models/51_voxel_head_model.ipynb— runs end-to-end on Colin27 with the precomputedfingertappingDOTfluence file: ~1.6M brain voxels → 921k after probe reduction → 501k after fluence reduction → 238k after sensitivity reduction; finalImageRecon.reconstructreturns dims(time, chromo, vertex)with the expected vertex count.Files
New
src/cedalion/dot/voxel_head_model.pytests/test_dot_voxel_head_model.pyexamples/head_models/51_voxel_head_model.ipynbModified
src/cedalion/dot/__init__.py— re-exportVoxelHeadModel.src/cedalion/dot/utils.py— addreduce_and_map_brain_voxels.src/cedalion/dot/head_model.py— extract two scalp helpers to module level; extendget_standard_headmodelwithkind="voxel".src/cedalion/dot/forward_model.py— widen the head-model type hint inForwardModel.__init__.src/cedalion/dot/image_recon.py—getattr(brain, "vertex_coords", {})guard so the parcel-aware SBF branch tolerates voxel brains.