Python port of Monalisa, the MattechLab toolbox for non-Cartesian and Cartesian MRI reconstruction, developed within the HES-SO Open Research Data programme (2026).
The port is faithful first: every upstream MATLAB function bmFoo of src/ becomes
monalisa/<same folder>/bmFoo.py with the same name, the same argument order, the same array
shapes (column-major) and the same precision (single → float32/complex64). Each ported function
is validated against golden recordings of the MATLAB implementation; see
docs/porting_method.md and CONVENTIONS.md.
-
Gridded reconstruction
bmMathilda(1D/2D/3D, with or without coil combination, cropping), the numba port of the gridding MEX kernels and the deapodizationbmK. -
Sparse gridding operators
bmTraj2SparseMat(Gn,Gu,Gut),bmSparseMat, forward/adjoint non-Cartesian Fourier operatorsbmShanna/bmNakatsha,bmNasha. -
Trajectories and volume elements: 3D phyllotaxis radial, Voronoi full/center-out radial 2D/3D, Cartesian, Voronoi box, random partial Cartesian.
-
Raw data: Siemens twix
.dat(viatwixtools) and ISMRMRD.mrdreaders with automatic steady-state detection. -
Coil sensitivity estimation from body + surface coil scans (automatic mask).
-
Binning and mitosius (data split into bins, MATLAB-compatible v7.3 files in both directions).
-
Iterative reconstructions: Sensa, Steva, Sleva; chain reconstructions TevaMorphosia, TevaDuoMorphosia and SensitivaMorphosia, with or without deformation matrices.
-
Demons registration for motion-compensated reconstructions (
bmImDeformFieldChain_imRegDemons23,bmImDeformFieldSheet_imRegDemons23): MATLAB'simregdemonsis replaced by SimpleITK's demons, so no MATLAB licence is needed; see docs/validation_registration.md. -
Tutorial 1 end to end: examples/tutorial_1/tutorial_1.ipynb (notebook with figures) or tutorial_1.py (script).
-
Image viewer and interactive steps: see below.
Live status: PORTING_STATUS.md.
With pip (any Python environment, 3.10 or newer):
git clone https://github.com/MattechLab/monalisa-python.git
cd monalisa-python
pip install -e ".[io,reg]" # numpy, scipy, numba, napari + raw-data readers + SimpleITK (registration)
pip install -e ".[io,reg,notebook]" # + JupyterLab, for the tutorial notebook
pip install -e ".[dev,io]" # + pytest, ruff (development)With conda (everything inside the environment, no admin rights needed):
conda create -n monalisa -c conda-forge python=3.12 matplotlib-base xcb-util-cursor
conda activate monalisa
pip install -e ".[io,reg,notebook]"The package installs as monalisa-python and is imported as monalisa.
Linux: library for the Qt windows. The napari viewer and the interactive steps use Qt, which on Linux with X11 needs the library libxcb-cursor (not available from pip). Either:
- in a conda environment:
conda install -c conda-forge xcb-util-cursor(monalisa loads it from the environment), or - system-wide:
sudo apt install libxcb-cursor0(Debian, Ubuntu) orsudo dnf install xcb-util-cursor(Fedora).
Without it, opening a window raises an error that says so; the notebook viewer works without it.
In a conda environment, install matplotlib-base, not conda's matplotlib: the latter brings
PySide6 with a second copy of Qt, whose libraries clash with napari's (PyQt6) and crash Python as
soon as a window opens. For the same reason, in a conda environment monalisa starts Qt with its
built-in theme (QT_QPA_PLATFORMTHEME=generic) instead of the GTK one, which would load the
system's GLib next to conda's; set QT_QPA_PLATFORMTHEME yourself to choose another.
bmImage(x, view) shows 2D to 5D images (or a list of frames) like the MATLAB viewer, with the
MATLAB axis order: x(:, :, k) with x down the rows, and sliders for z and time.
view |
Shows | Where |
|---|---|---|
"inline" |
sliders and the slice below the cell | Jupyter (default there) |
"window" |
a napari window: one slice view, views along x/y/z, contrast, points | anywhere (default outside Jupyter) |
"3d" |
the same window, opened in 3D (volume rendering, rotate with the mouse) | anywhere |
"ortho" |
one window laid out like ITK-SNAP: slices along z, x, y and the 3D view, linked by a cursor (click in a slice view to move it) | anywhere |
The windows also open from Jupyter, next to the notebook. Other parameters: name (title),
contrast=(low, high), block=True (in a script, keep the window open until it is closed; not
needed in Jupyter) and viewer (add the image to an earlier "window" / "3d" window to compare
reconstructions: side by side in one row, in the order added, with shared sliders; the grid button at the
bottom left stacks them instead).
import monalisa as ml
ml.bmImage(x_cs) # list of 3D frames -> x, y, z, t
ml.bmImage(x_cs, "3d", name="TevaMorphosia")
ov = ml.bmImage(x_cs, "ortho") # 3 slices + 3D, like ITK-SNAP
ov.set_point([40, 36, 41, 0]) # move the cursor from code (0-based x, y, z, t); ov.point reads it
v = ml.bmImage(x_sensa, "window", name="Sensa")
ml.bmImage(x_cs, viewer=v, name="TevaMorphosia") # same window, side by side: Sensa left, CS right
ml.bmImage(x0, "window", block=True) # in a script: keep the window open until closed
mask = ml.roipoly(x0[:, :, 40]) # draw a polygon, like MATLAB roipolyAs in MATLAB, autoFlag=False opens windows where the automatic choices can be checked and
changed: the acquisition parameters (raw data readers), the coil-mask thresholds and the bounding
box (bmCoilSense_nonCart_mask_automatic). Every value chosen in a window is printed and can be
set in the code instead, which needs no window and is reproducible:
| Window | Set it in the code |
|---|---|
metadata check (createRawDataReader(f, False)) |
createRawDataReader(f, True), then reader.acquisitionParams.nShot_off = 14 (any field) |
| coil-mask thresholds and box | bmCoilSense_nonCart_mask_automatic(y, Gn, True, th_RMS, th_MIP, borders) |
normalization ROI (roipoly(im)) |
roipoly(im, c, r) with the printed vertices, or any boolean mask |
import scipy.io as sio
d = sio.loadmat("data.mat") # MATLAB -v7 and older (the default of MATLAB save)
from monalisa.readWrite._mat73 import loadmat73
r = loadmat73("reconstructions.mat") # -v7.3 (HDF5): large files, and the files written hereloadmat returns MATLAB logical arrays as uint8: convert masks with .astype(bool) before
indexing with them.
import scipy.io as sio
import monalisa as ml
d = sio.loadmat("monalisa/demo/data_demo/data_2_static_radial_simulated_from_cartesian/data.mat")
ve = ml.bmVolumeElement(d["t"], "voronoi_full_radial2")
C = ml.bmImResize(d["C"], [32, 32], d["frSize"])
x0 = ml.bmMathilda(d["y"], d["t"], ve, C, d["N_u"], d["frSize"], d["dK_u"])Tutorial 1 (3D radial brain scan, Siemens raw data):
python demo/data_demo/data_8_tutorial_1/download_data.py # raw data, about 7.9 GB (once)
jupyter lab examples/tutorial_1/tutorial_1.ipynb # or:
python examples/tutorial_1/tutorial_1.py --data demo/data_demo/data_8_tutorial_1 --out results
python examples/tutorial_1/tutorial_1.py --out results --show # view the saved resultspytest # unit tests + replay of all MATLAB recordings found in fixtures/matlabMATLAB recordings are produced with tools/matlab_capture (requires MATLAB and the upstream
repository at the commit in monalisa.UPSTREAM_COMMIT):
matlab -batch "addpath('tools/matlab_capture'); run_capture('static2d', fullfile(pwd,'fixtures','matlab'), '/path/to/monalisa')"Scenarios: static2d, volume_elements, tutorial_coilsense, ismrmrd, tutorial_recon
(see tools/matlab_capture/scenarios). The end-to-end comparison of tutorial 1 is opt-in:
MONALISA_E2E_DATA=/path/to/data_8_tutorial_1 pytest tests/e2e -s.
Apache License 2.0, see LICENSE.