Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 is MRX: 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.

NameDescriptionExample
BoolCan be true or false.use_gpu=true
Int64bit signed integer. Ranges from \(-2^{63}\) to \(2^{63} - 1\).spin_count=1000
FloatDouble-precision (64 bit) IEEE 754 floating point value.latent_signal_threshold=0.1
ComplexEncoded as real and imaginary FloatCurrently not exposed
SignalList of per-coil lists of Complex ADC samples.See Signal
EncodingEncoding of ADC samples, stored as 4 Floats.See Encoding
VoxelPhantomFlattened list of voxels + positionsSee VoxelPhantom
VoxelGridPhantomCartesian 3D grid of voxelsSee VoxelGridPhantom
DiscreteEventSeqSequence of instantaneous eventsSee DiscreteEventSeq
ContinuousBlockSeqPulseq-like sequence of blocksSee 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:

PropertyDescription
pdProton density \([a.u.]\)
t1\(T_1\) relaxation time \([s]\)
t2\(T_2\) relaxation time \([s]\)
t2dash\(T_2'\) dephasing time \([s]\)
adcApparent diffusion coefficient \([10^{-3} mm^2 / s]\)
b0\(B_0\) off-resonance frequency \([Hz]\)
b1Relative \(B_1\) field strength
coil_sensCoil sensitivity \([a.u.]\)

TissueProperties

Structure containing basic signal types, used by the kspace extract tool. Contains four Floats:

PropertyDescription
t1\(T_1\) relaxation time \([s]\)
t2\(T_2\) relaxation time \([s]\)
t2dash\(T_2'\) dephasing time \([s]\)
pdProton 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 by size=[sx, sy, sz].
  • AABox(size): An axis-aligned box of size size=[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 Floats 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 Floats 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 pulse
  • Fid { kt }: Free induction decay, which will apply relaxation and decay as given by kt=[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 and apoditzaiton value

MRX: Pulseq

TODO

A rudimentary pulseq API is currently included in [toolapi]. It should be extracted into its own package by copying pulseq-zero. This package should be toolapi - 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 the toolapi.

Tools

The following list of tools exists:

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.

InputType
sequenceDiscreteEventSeq
tissueTissueProperties
min_magFloat

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
InputType
sequenceDiscreteEventSeq
phantomVoxelPhantom
spins_per_voxelInt
multithreadedBool

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.

InputType
min_state_magFloat
max-state_countInt
min_latent_signalFloat
min_emitted_signalFloat
use_gpuBool
phantomVoxelPhantom
sequenceDiscreteEventSeq

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: