This project is an artifact from the master's thesis of Sander Tøkje Hauge and Martin Hegnum Johannessen.
Tentative thesis title: Exploring Cross-Species Neuroanatomy in Augmented Reality.
Tentative subtitle: A reproducible pipeline and Nevrolens extension for interactive cross-species neuroanatomy on Meta Quest 3.
The thesis is completed at NTNU through TDT4900 - Computer Science, Master's Thesis, with delivery in June 2026.
Cross-species cortical surface morphing for neuroscience visualization in Nevrolens.
The pipeline starts from neuroatlas-prep part meshes, builds pair-specific anatomical correspondence, extracts and aligns cortical surfaces, instantiates region labels, and exports OBJ morph frames.
This repository uses both deformation and morphing terminology. Deformation alters a single object's form through stretching or bending, whereas morphing smoothly transforms one entirely different object into another. The current pipeline is technically closer to deformation because it deforms one mesh toward another through correspondence fields. The project name keeps "morphing" because the intended end goal is a smooth cross-species morph from one neuroanatomical surface to another.
| Species | Atlas | Input source |
|---|---|---|
| Mouse | Allen CCFv3 | neuroatlas-prep |
| Rat | Waxholm Space v4 | neuroatlas-prep |
| Marmoset | MBM v3 vL | neuroatlas-prep |
| Macaque | D99 v2.0 | neuroatlas-prep |
| Human | CerebrA Brain | neuroatlas-prep |
Requires Python 3.11, just, and neuroatlas-prep outputs.
Install just on macOS:
brew install justIf Homebrew is not available, install through Cargo:
cargo install justpython3.11 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtIf python does not resolve to .venv/bin/python, run scripts through .venv/bin/python or fix the shell PATH/alias first.
The normal entry point is the just recipe. It isolates generated files under output/pair_runs/{source}_to_{target}/ and runs Scripts 1-5.
just morph rat marmosetThe recipe uses .venv/bin/python, so create and install the virtual environment first.
Add --render-video to run Script 6 after frame export:
just morph-video rat marmosetDirect Python usage is also supported:
python scripts/run_pipeline.py --source rat --target marmoset --frames 30 --cleanTo run stages manually:
python scripts/1_build_correspondence.py --create --source rat --target marmoset
python scripts/2_extract_cortical_surfaces.py --species rat marmoset
python scripts/3_align_cortical_surfaces.py --source rat --target marmoset
python scripts/4_instantiate_anatomy.py --species rat marmoset
python scripts/5_morph_cortical_surfaces.py --source rat --target marmosetRender a video manually after frames exist:
python scripts/6_render_morph_video.py --source rat --target marmoset| Script | Purpose | Main output |
|---|---|---|
1_build_correspondence.py |
Build or validate pair-specific anatomical correspondence | output/1_correspondence/*.json |
2_extract_cortical_surfaces.py |
Prepare pair-aware cortical meshes and part metadata | output/2_cortical_surfaces/ or pair-run equivalent |
3_align_cortical_surfaces.py |
Align source to target with ICP | output/3_aligned_meshes/ |
4_instantiate_anatomy.py |
Convert part metadata into per-region vertex sets | output/4_anatomy/*_regions.json |
5_morph_cortical_surfaces.py |
Build correspondence/deformation field and export frames | output/5_morph_sequence/ |
6_render_morph_video.py |
Render exported frames | video output |
Configuration lives in config/.
| File | Controls |
|---|---|
config/species_data.yaml |
Species paths, coordinate transforms, extraction settings |
config/pipeline.yaml |
Output paths, decimation, visualization, logging |
config/alignment.yaml |
ICP alignment settings |
config/morphing.yaml |
Correspondence and morphing settings |
The project-level config.yaml is optional. If it exists, it is treated as an override on top of config/*.yaml.
This README is the active documentation. Files under docs/archive/ are historical notes and should not be treated as current pipeline instructions.
The current Script 5 path is intentionally narrow:
| Setting | Meaning |
|---|---|
region_constraints.enabled |
Use anatomical region-constrained correspondence. Disable only for pure NN baseline runs. |
balanced_physical_weight |
Penalizes long source-to-target jumps inside a region. |
balanced_target_edge_weight |
Encourages boundary vertices to stay near target region boundaries. |
side_aware_matching |
Splits bilateral regions by left/right side before matching. |
use_curvature_aware |
Adds curvature as a mild matching feature for folded cortex. |
deformation_field |
Smooths matched displacement vectors within regions before frame export. |
experimental.tps_init |
Optional TPS pre-warp, kept as future/experimental work. |
experimental.blend_with_global_nn |
Optional blend with global NN, not part of the current best default path. |
Several older knobs are now internal defaults, not config options: balanced NN, normalized local coordinates, local frame matching, unused-target preference, and side axis x.
Change pair:
just morph mouse ratReuse an existing correspondence file:
python scripts/run_pipeline.py --source mouse --target rat --reuse-correspondenceChange mesh density:
# config/pipeline.yaml
decimation:
target_vertices: 100000Run headless:
# config/pipeline.yaml
visualization:
show_visualizations: falseThe pipeline runner writes a temporary runtime config that sets visualizations off automatically.
Pipeline runs write isolated outputs to:
output/pair_runs/{source}_to_{target}/
├── 1_correspondence/
├── 2_cortical_surfaces/
├── 3_aligned_meshes/
├── 4_anatomy/
└── 5_morph_sequence/
Script 5 exports same-topology OBJ frames and sequence_info.json. Script 6 can render those frames to MP4 with:
just morph-video rat marmosetTo use frames in Nevrolens, copy the frame directory into the Unity resources path configured in config/pipeline.yaml:
output:
unity_export_dir: "../nevrolens/Assets/Nevrolens/Resources/MorphSequences"Unity import settings for frame OBJs:
| Setting | Value |
|---|---|
| Read/Write Enabled | On |
| Mesh Compression | Off |
| Generate Colliders | Off unless needed |
| Normals | Import |
All frames in one sequence should use the same import settings.
Inspect the first, middle, and final frames after Script 5. Good morphs keep regions on the correct side, preserve boundaries, avoid large spikes or deep creases, and end close to the target shape.
Useful checks:
python scripts/visualize_region_correspondence.py --source rat --target marmoset --region Motor
open -a MeshLab output/pair_runs/rat_to_marmoset/5_morph_sequence/frame_0015.objFor difficult pairs, compare deformation_field: true against false, and try use_midpoint_pivot: true when direct A-to-B morphing is visually jumpy.
Landmark-based morphing is legacy. The standard pipeline uses anatomical regions from Script 4 and balanced region correspondence in Script 5. Historical landmark utilities remain in utils/landmark_utils.py and scripts/legacy/, but they are not used by just morph.
ModuleNotFoundError from a script usually means the wrong Python executable is running. Use:
source .venv/bin/activate
which python
python -m pip install -r requirements.txtIf which python still points to Anaconda or /usr/bin/python3, run scripts as .venv/bin/python ... until the shell configuration is fixed.
If Script 3 alignment looks poor, inspect output/3_aligned_meshes/{source}_to_{target}/alignment_metadata.json and the aligned OBJ files. Script 3 currently uses ICP only.
If Script 5 creates many inverted faces, first compare runs with and without deformation_field, then try use_midpoint_pivot: true. The current correspondence field is strongest for adjacent species pairs and still imperfect for large lissencephalic-to-gyrencephalic jumps.
The pipeline depends on neuroatlas-prep for harmonized part meshes and uses PyMeshLab for mesh decimation/remeshing. The runner validates the prepared input directories before processing and can cache them into a run with --cache-prep-inputs. It no longer depends on mesh-standardizer.