MRX - MR Expertise
Funded by START-interaktiv (BMFTR, February 2026 - January 2029).
3 - year - goal of MRX
Creating
MRX: Assistant
, an intelligent AI assistant, supporting clinic personell with MR measurements. An LLM allows easy interaction without prior training. A deep integration with the scanner means full data insight and quick interaction with the user. An extensive, physics based world model delivers facts instead of hallucinations.
The necessary tools for this project will also form the foundation of future applications of MRX: Sequence development, optimizing for low-cost hardware, training of new reconstruction networks and more.
Building Blocks
The foundation of MRX: Assistant
consists of various building blocks.
These are developed independent of each other.
This allows to use them to construct new products which go beyond the target of an assistant.
MRX: ToolAPI
: Connect any application with any MRX world-model tool, easily. Allows quick experimentation: comparing implementations, testing tools in various scenarios. Developed first as it helps with the development of the other building blocks.MRX: Pulseq
: Create sequences with a fully pypulseq copmatible API. Supports.seq
files and isMRX: ToolAPI
compatible.- Tools: Tools built in Rust and Python, using the
MRX: ToolAPI
. - Apps: Apps using different Tools to build Products.
MRX: ToolAPI
aka "Universal Simulation Interface"
TODO
For consistency and better naming, all MRX projects should be named MRX: xyz
.
This means that USI changes to MRX: ToolAPI
, which makes the goals of USI clearer as well.
Replace all occurences in the book and in the whole mrx
repo, then delete this warning.
This projects makes it possible to easily:
- switch tools (e.g: different simulations) in an application with little code changes
- use a tool in many applications.
Therefore alternative implementations of a tool can be compared, which is helpful in development. Tools can also be shared and reused, as well as get tested in many environments. This is made possible through an interface, which is coded intependently of tools and applications. It is the "contract" between those two, a simple API that is quickly implemented on both ends and reduces communication to a fixed set of easy to understand value types.
Tool
A tool is a standalone program, which can be executed from the command line.
Examples are a combined library ("bloch_sim.exe"
) or a python script ("python sar_calc.py"
).
The tool retrieves its inputs via the MRX: ToolAPI
, does its computations (optionally reporting progress) returns the output via the same API, then shuts down.
This makes writing tools very simple: It can be written in any language supported by MRX (currently Rust and Python).
A short, self contained file or script is sufficient, the result is a small, independent executable program or script.
Host
The host can be any environment that executes tools. Examples are:
- A self-contained program like a viewer or GUI for MR tasks
- A shim between the tools and LLMs, exposing the capabilities of the tools via MCP
- A python script, optimizing a sequence using simulation and reconstruction tools
- A jupyter notebook session, interactively experimenting with MR ideas with the help of tools.
The MRX: ToolAPI
makes it easy to define all input parameters, call the tool, poll progress and retrieve its output.
Errors of the tool are passed to the Host.
From a programming point of view, calling a tool feels very similar to calling a function.
The limited set of possible parameter types makes it possible to easily exchange tools with similar purposes, even if they were programmed completely independently from different people.
Connection
The exact method of communication between Host and Tool is deliberately opaque.
This avoids lock-in into a specific host-tool combination, exposing the exchange of values as the only observable effect of MRX: ToolAPI
.
It also makes it possible to iterate and improve the exchange method without requring any code changes to the Host or the Tool.
Currently, the project opens an inter-process channel (Windows named pipe or UNIX domain socket), serializes all values to json and passes it over the channel.
Value types
USI needs to exchange data between the Host and the Tool. Namely, upon calling the tool, the host will send a bunch of inputs. After finishing, the tool will return an output. To facilitate the unification of USI tools, this exchange is done via a canonical list of types, which carry a semantic meaning.
Name | Description | Example |
---|---|---|
Bool | Can be true or false. | use_gpu=true |
Int | 64bit signed integer. Ranges from \(-2^{63}\) to \(2^{63} - 1\). | spin_count=1000 |
Float | Double-precision (64 bit) IEEE 754 floating point value. | latent_signal_threshold=0.1 |
Complex | Encoded as real and imaginary Float | Currently not exposed |
Signal | List of per-coil lists of Complex ADC samples. | See Signal |
Encoding | Encoding of ADC samples, stored as 4 Float s. | See Encoding |
VoxelPhantom | Flattened list of voxels + positions | See VoxelPhantom |
VoxelGridPhantom | Cartesian 3D grid of voxels | See VoxelGridPhantom |
DiscreteEventSeq | Sequence of instantaneous events | See DiscreteEventSeq |
ContinuousBlockSeq | Pulseq-like sequence of blocks | See ContinuousBlockSeq |
Bool
Int
Float
Complex
Signal
List of per-coil signals. For every coil: List of complex ADC sample values. Each complex value consists of two Float's (real and imaginary value).
Example: a measurement of a 64x64 sequence on a 8-channel system will return a list of 8 lists, each of which contains 4096 complex values. A single-coil mesurement would return a list containing one element: a list with 4096 samples.
Encoding
List of [x, y, z, t]
elements: 4 Float's per ADC sample that store their kt - encoding.
xyz
represents the \(\vec{k}\) dephasing of the measured samples (unit: \(1 / m\)).
t
is the \(\tau\) dephasing: effect of \(B_0\) inhomogeneities and \(T'_2\) (unit: \(s\)).
Phantom
Phantoms consists of many voxels, each of which describe the physical properties of the spatial location they sample. The shape of these voxels are described by their VoxelShape. Their location is specified differently for the VoxelPhantom and the VoxelGridPhantom.
Each sample gives the following values:
Property | Description |
---|---|
pd | Proton density \([a.u.]\) |
t1 | \(T_1\) relaxation time \([s]\) |
t2 | \(T_2\) relaxation time \([s]\) |
t2dash | \(T_2'\) dephasing time \([s]\) |
adc | Apparent diffusion coefficient \([10^{-3} mm^2 / s]\) |
b0 | \(B_0\) off-resonance frequency \([Hz]\) |
b1 | Relative \(B_1\) field strength |
coil_sens | Coil sensitivity \([a.u.]\) |
TissueProperties
Structure containing basic signal types, used by the kspace extract tool. Contains four Floats:
Property | Description |
---|---|
t1 | \(T_1\) relaxation time \([s]\) |
t2 | \(T_2\) relaxation time \([s]\) |
t2dash | \(T_2'\) dephasing time \([s]\) |
pd | Proton density \([a.u.]\) |
VoxelShape
Most phantoms consist of many voxels with identical shapes. Currently, the supported shapes are:
AASinc(size)
: An axis-aligned sinc shape where the first zero crossing is given bysize=[sx, sy, sz]
.AABox(size)
: An axis-aligned box of sizesize=[sx, sy, sz]
.
VoxelPhantom
The voxel phantom is the simplest version of phantoms:
It is specified by a list of values for each Phantom property, combined with a single voxel_shape
property that is applied to all voxels, as well a list of voxel positions.
This pos
list contains three Float
s per voxel. All lists have the same length.
For partial volume effects, multiple voxels can coexist at the same position.
This value type makes the implementation of simulation tools very easy which operate on each voxel individually.
VoxelGridPhantom
Phantoms are usually defined as cartesian grids of voxels. This makes reslicing, interpolation or truncating of the phantom easy, as well as FFT transformations.
The voxel grid phantom doesn't store a list of voxel positions (pos
), but instead has a grid_spacing
property, which consists of three Float
s that define the cell size in the cartesian grid.
Combined with the grid_size
property, which is tree integers that define the resolution of the grid, the mapping of the flattened arrays of physical properties and the 3D - matrix of voxels is defined.
This phantom type is otherwise identical to the VoxelPhantom and has a function to convert to it.
DiscreteEventSeq
This sequence type consists of a chronological list of events, which are applied sequentially and never simultaneously. There are three event types:
Pulse { angle, float }
: An instantaneous RF pulseFid { kt }
: Free induction decay, which will apply relaxation and decay as given bykt=[kx, ky, kz, t]
Sample { phase }
: An ADC sample which captures the current signal while applying a roatation
This sequence type is optimized to be used by simulations which do not care about the pulse shape or exact gradient trajectory, but rather gradient moments and timings of samples / pulse centers.
ContinuousBlockSeq
This sequence type is modelled after Pulseq, consisting of a sequence of blocks, each of which can contain multiple events which are applied in parallel.
The blocks each contain (optional) values for min_duration
, rf
, gx
, gy
, gz
and adc
.
This sequence type can be used for very accurate simulations, that incorporate pulse shapes, slice selection and more, as well as tools for SAR calculation and more.
Otherwise it mainly exists to construct sequences with pulseq, using it as backing data type.
It can be converted to a DiscreteEventSeq, as provided by MRX: ToolAPI
.
Pulse
Pulses are defined by their timing: delay
, duration
and ringdown
,
as well as their signal: flip_angle
, phase_offset
and frequency_offset
.
In addition, pulses have a shape, where the following exist:
- Block: no additional parameters
- Sinc: given by a
time_bandwidth_product
andapoditzaiton
value
MRX: Pulseq
TODO
A rudimentary pulseq API is currently included in [
toolapi
]. It should be extracted into its own package by copyingpulseq-zero
. This package should betoolapi
- compatible while being able to write.seq
files without having dependencies outside of MRX.Contrary to
pulseq-zero
,MRX: pulseq
will initially not be differentiable until support has landed in thetoolapi
.
Tools
The following list of tools exists:
- kspace extract: A PDG-based kspace trajectory detection tool
- basic bloch sim: A simple isochromat-based simulator
- mr0 pdg sim: The mr0 simulation, exposed as tool.
k-Space extraction
kspace_extract.exe
A tool for extracting the k-space from a sequence. Internally uses a PDG simulation based on the provided tissue properties. Together with a rough, exponential signal fall-off estimation, the simulation determines the strongest state for every ADC sample. The dephasing of this state is returned as the encoding of the sample.
Input | Type |
---|---|
sequence | DiscreteEventSeq |
tissue | TissueProperties |
min_mag | Float |
The returned output is the Encoding of the sequence.
Basic Bloch simulation
basic_bloch_sim.exe
Very simple, isochromat-based simulation written in Rust.
This tool is purposely simple, trying to provide a ground truth implementation of a Bloch simulation and an example for a MRX: ToolAPI
tool.
The simulation currently has the following limitations:
- no GPU support
- box voxels: voxel shape of phantom is ignored, leads to aliasing in combination with gradient spoiling, high spin count needed
- not differentiable
- only single-coil acquisition is supported
Input | Type |
---|---|
sequence | DiscreteEventSeq |
phantom | VoxelPhantom |
spins_per_voxel | Int |
multithreaded | Bool |
If the multithreaded
parameter is set, the phantom is split into N parts with identical number of spins where N is the number of logical processors on the system.
Returns the measured single-coil Signal.
MR-zero PDG simulation
python mr0sim/main.py
Exposes the MR0 PDG simulation as a python script that is MRX: ToolAPI
compatible.
Input | Type |
---|---|
min_state_mag | Float |
max-state_count | Int |
min_latent_signal | Float |
min_emitted_signal | Float |
use_gpu | Bool |
phantom | VoxelPhantom |
sequence | DiscreteEventSeq |
If the use_gpu
parameter is set, the simulation will use CUDA to run on the GPU selected by PyTorch.
This might crash if the current hardware does not support CUDA (e.g.: no compatible NVIDIA graphics card was detected).
Returns the measured Signal.
Apps
Contributors and Licenses
MRX is created by:
- Jonathan Endres
- Simon Weinmüller
Parts of it are free for non-commercial use: