Skip to content

Use the official bifti package for BIfTI phantoms - #202

Merged
J-Endres merged 3 commits into
mainfrom
claude/eloquent-heisenberg-60j1f0
Oct 28, 2026
Merged

J-Endres merged 3 commits into
mainfrom
claude/eloquent-heisenberg-60j1f0

Conversation

@J-Endres

@J-Endres J-Endres commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #201.

Replaces the internal BIfTI phantom config, NIfTI loader and NIfTI writer with the published bifti package (>= 0.3.0).

Changes

  • Dependency: adds bifti>=0.3.0; removes ~480 lines of internal code.
  • Python: requires-python raised from >=3.10 to >=3.12, since bifti requires 3.12 (and numpy ≥ 2.5.1).
  • phantom/nifti_phantom.py: now only re-exports the bifti classes. NiftiPhantom, NiftiTissue and ResliceConfig remain as aliases of BiftiPhantom, BiftiTissue and ResliceTo, so existing user code and brainweb generation keep working. mr0 additionally exports BiftiPhantom, BiftiTissue, ResliceTo, Patient, PhantomUnits and PhantomSystem (the last two were documented but not previously exported).
  • TissueDict.load: uses the bifti loader. Behavioural differences:
    • resampling to reslice_to is density-weighted averaging instead of trilinear interpolation with slab averaging, so resliced values can differ slightly;
    • the phantom's patient position is applied to the affine (no patient → unchanged);
    • func mappings are parsed by bifti's restricted grammar instead of eval; complex B1+/B1- maps keep their phase.
  • TissueDict.save: now converts to bifti.NumpyTissues and calls bifti.NumpyPhantom.save(..., constant_threshold=1e-5). Differences to before:
    • NIfTIs are named <json name>_<property>.nii.gz (e.g. phantom_B1plus_0.nii.gz instead of phantom_B1+.nii.gz), still one file per property with tissues stacked;
    • data is stored as float64 / complex128 (was float32 / complex64), so files are larger;
    • the JSON has no reslice_to and tissues are written in sorted name order;
    • $schema points to the official schema URL.
  • Docs (api-nifti.md, api-phantom.md, api.md) and CHANGELOG updated.
  • Version: bumped to 1.3.0 (Cargo.toml, Cargo.lock, pyproject.toml, CHANGELOG).

Testing

Run locally without building the Rust extension (CPU torch, bifti 0.3.0):

  • TissueDict.save() → TissueDict.load() round-trip of a two-tissue phantom reproduces all maps and the affine exactly; .build() works.
  • A phantom with complex two-channel B1+ round-trips exactly (phase preserved), with warnings promoted to errors.
  • Two phantoms saved to the same directory get separate files.
  • A phantom JSON saved by the old code loads with the new code.
  • Reslicing to a coarser grid with patient=HFS gives the expected shape and affine; tissue densities still sum to 1.

The simulation test workflow and notebooks were not run locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NHKPBcYamTCDbaAVtBYRMR

Replace the internal phantom config dataclasses and NIfTI loader with the
published `bifti` package. NiftiPhantom, NiftiTissue and ResliceConfig are
kept as aliases of the bifti classes, and TissueDict.load now uses the
bifti reference loader (density-weighted resampling, patient position).

bifti requires Python >= 3.12, so requires-python is raised accordingly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHKPBcYamTCDbaAVtBYRMR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHKPBcYamTCDbaAVtBYRMR
bifti 0.3.0 loads complex B1 maps, names saved NIfTIs after the JSON and
can store near-constant maps as values, so the hand-written NIfTI writer
in TissueDict.save is no longer needed.

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

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Simulation test: main (before) vs. this PR (after)

Sequence Status max mag NRMSE max phase RMSE max runtime Δ
mr0_DREAM_STE_seq ✅ 0.00e+00 0.00e+00 +4.7%
mr0_DWI_SE_EPI ✅ 0.00e+00 0.00e+00 +0.9%
mr0_EPI_2D_seq ✅ 0.00e+00 0.00e+00 +5.1%
mr0_FID_seq ✅ 0.00e+00 0.00e+00 +5.9%
mr0_FLASH_2D_seq ✅ 0.00e+00 0.00e+00 +3.0%
mr0_GRE_to_FLASH ⚠️ – – –
mr0_RARE_2D_seq ✅ 0.00e+00 0.00e+00 +7.8%
mr0_SE_CPMG_seq ✅ 0.00e+00 0.00e+00 +14.4%
mr0_STE_3pulses_5echoes_seq ✅ 0.00e+00 0.00e+00 +3.6%
mr0_TSE_2D_multi_shot_seq ❌ 0.00e+00 0.00e+00 +32.3%
mr0_bSSFP_2D_seq ✅ 0.00e+00 0.00e+00 +6.3%
mr0_diffusion_prep_STEAM_2D_seq ✅ 0.00e+00 0.00e+00 -8.8%
✅ mr0_DREAM_STE_seq: All tests passed.

mr0_DREAM_STE_seq

✅ mr0_DWI_SE_EPI: All tests passed.

mr0_DWI_SE_EPI

✅ mr0_EPI_2D_seq: All tests passed.

mr0_EPI_2D_seq

✅ mr0_FID_seq: All tests passed.

mr0_FID_seq

✅ mr0_FLASH_2D_seq: All tests passed.

mr0_FLASH_2D_seq

⚠️ mr0_GRE_to_FLASH: No comparison possible: [Errno 2] No such file or directory: '/home/runner/work/MRzero-Core/MRzero-Core/tests/simulation_test/ref_files/mr0_GRE_to_FLASH.seq.npz'
✅ mr0_RARE_2D_seq: All tests passed.

mr0_RARE_2D_seq

✅ mr0_SE_CPMG_seq: All tests passed.

mr0_SE_CPMG_seq

✅ mr0_STE_3pulses_5echoes_seq: All tests passed.

mr0_STE_3pulses_5echoes_seq

❌ mr0_TSE_2D_multi_shot_seq: Runtime deviation too high (32.26%) at acc=0.60000

mr0_TSE_2D_multi_shot_seq

✅ mr0_bSSFP_2D_seq: All tests passed.

mr0_bSSFP_2D_seq

✅ mr0_diffusion_prep_STEAM_2D_seq: All tests passed.

mr0_diffusion_prep_STEAM_2D_seq

Workflow run — raw .npz data is attached as artifacts.

@J-Endres
J-Endres merged commit 7ba51cb into main Oct 28, 2026
31 of 32 checks passed
@J-Endres
J-Endres deleted the claude/eloquent-heisenberg-60j1f0 branch October 28, 2026 13:21
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.

use official bifti crate

2 participants