Skip to main content

turbotwix

A fast, and opiniated, Siemens TWIX (.dat, VB and VD/VE) reader.

  • 6x faster than pymapvbvd on a full Cartesian read, and able to pull a single shot out of a 24 GiB file in a couple of milliseconds without ever loading the rest.
  • Use modern python (type annotations) and numpy structured dtype array.
  • NB: It does not implement slice-geometry parsing or ramp-sampling regridding.

Disclosure

This library is not affiliated with Siemens, and the TWIX format is not documented by them. The format has been reverse-engineered from the files and the existing implementations:

Without them, turbotwix would not exists.

Usage

import turbotwix as tw

f = tw.open_twix("meas.dat")
f  # TwixFile(..., measurements=['AdjCoilSens', 'bold_spiral...'])

lines = f.lines  # one strided read of the line headers
lines.image  # LineTable: imaging lines only
lines.noise  # noise-calibration lines, for pre-whitening
len(lines.image), lines.shape  # 4800, (44, 15000)

samples = f.read(lines.image)  # (4800, 44, 15000) complex64

f.lines, f.hdr and f.read act on the last measurement (f.scan), which is the scan itself, the measurements before it are usually calibration data you don't necessary need. They are still there, by index or by iteration:

len(f)  # 2
f[0].protocol_name  # 'AdjCoilSens'
noise = f[0].lines.noise  # a calibration measurement, explicitly

Selections are boolean queries and compose, so a partial read is just a smaller selection. Nothing else is touched on disk:

img = f.lines.image
rep0 = img[img.counter("Rep") == 0]  # one volume's shots
vol = f.read(rep0)  # (40, 44, 15000), 201 MiB
shot = f.read(img[5:6])  # one shot, whatever the file's size

lines.has(tw.Flag.REFLECT)  # any of the 64 eval-info bits, by name
lines.image.headers()  # full scan headers on demand: SliceData,
# IceProgramPara, timestamps, centre indices

Read into your own buffer to bound memory on files that do not fit in RAM. The copy goes straight from the mapped file into it, with no intermediate buffer:

buf = np.empty((len(rep0), 44, 15000), dtype=np.complex64)
for r in np.unique(img.counter("Rep")):
    sel = img[img.counter("Rep") == r]
    f.read(sel, out=buf[: len(sel)])
    ...

The header/text protocol is parsed per buffer on first access, by attribute or by key:

f.hdr.Meas.alTR[0]
f.scan.protocol_name, f.scan.patient_name

A tuple indexes as a nested path in one step, .get is path-aware too, and search_header_for_val finds a key wherever it sits in the tree (an exact, structural walk -- not pymapvbvd's regex/substring scan over a flattened namespace):

f.hdr.Phoenix["sSliceArray", "asSlice", 0, "dThickness"]
f.hdr.Phoenix.get(("sKSpace", "lPartitions"), default=1)
tw.search_header_for_val(f.hdr.Phoenix, "sFastImaging", "lTurboFactor")

Grid-size counters (NLin, NPar, ...) are available on any line selection, computed and cached on first access:

f.lines.image.NLin, f.lines.image.NCha  # 44, 4

You can also use a context manager if you want:

with tw.open_twix("meas.dat") as f:
    samples = f.read(f.lines.image)

The data model

A TWIX measurement is a list of acquisition lines, each with metadata and a (ncha, ncol) block of samples. turbotwix hands you exactly that: a queryable line table, and reads that return (ncha, n_lines, ncol) — channel first, matching its on-disk order, contiguous.

You can query and filter the lines, and when you want it get a numpy array of the data you need

PMU

Physiological (ECG/pulse/respiration) data interleaved with the acquisition, if any:

pmu = f.scan.pmu  # empty if this measurement carries no PMU data
pmu.signal["ECG1"]  # normalized waveform
pmu.trigger["PULS"]  # matching boolean trigger channel
pmu.timestamp["ECG1"]  # per-sample clock, 2.5 ms ticks since midnight

Cartesian data

dense = f.read(dims=("Lin", "Par"))  # (Lin, Par, Cha, Col)

read(dims=...) raises if several lines land on the same grid position — that normally means a counter is missing from dims, not that the data wants averaging — and names the counters responsible. dims="minimal" picks the axes for you: the counters that vary, minus those the others already determine.

Correctness

turbotwix is checked against two existing readers:

they are the prior art for this format, and the ground truth tests/test_parity.py verifies against.

turbotwix vs. twixtools vs. pymapvbvd

turbotwix twixtools pymapvbvd
Language Python (numpy, mmap) Python Python/MATLAB
Full Cartesian read fastest (~6x pymapvbvd) slower slower
Partial / out-of-core read yes, mmap-backed, no full load no, reads the whole file no, reads the whole file
Line selection / query API boolean queries over a line table list of dicts struct/index based
Slice-geometry parsing no yes no
Ramp-sampling regridding no yes yes
Oversampling removal no yes yes
PMU (ECG/pulse/resp) parsing yes yes no

turbotwix trades the signal-processing conveniences (regridding, oversampling removal, geometry) for raw read speed and the ability to pull a small selection out of a file that doesn't fit in RAM. twixtools and pymapvbvd remain the more complete choice when you need those conveniences and can afford to load the full measurement.

Performance

Full read of a 234 MB Cartesian measurement, cold page cache, each library in its own process (scripts/bench_read.py):

library time (s) peak anon (MB) mmap (MB) maxrss (MB)
turbotwix 0.35 140 159 296
pymapvbvd 0.82 192 38 228
twixtools 0.69 206 41 242

The case this doesn't show is the one the design is really for: a selection out of a file far larger than RAM, where the reference readers have no choice but to assemble the whole array first. On a 24 GiB interleaved spiral measurement, turbotwix builds the line table in ~65 ms and pulls a single shot out of it in ~3 ms — pymapvbvd and twixtools have no equivalent operation, since without a k-space grid there is nothing partial to fold onto.

The methodology and where the speed comes from are in docs/implementation.md.

Known limitations

  • A measurement whose line offsets are not 8-byte aligned raises UnsupportedLayoutError.
  • Lines of differing (ncha, ncol) are tabled together but cannot be read in one call; select a single-shaped subset.
  • SYNCDATA blocks are not ADC data and never appear in the line table; PMU ones decode separately via Measurement.pmu.
  • No ramp-sampling regridding or slice-geometry parsing.
  • No oversampling removal: it is signal processing (an FFT round-trip), and along a non-Cartesian readout it is not meaningful.

Documentation

Development

uv sync                    # numpy + dev tools only
uv run pytest
uv run ruff check .
uv run ty check src
uv sync --group parity     # to also run tests/test_parity.py against real references

Metadata

Release files for turbotwix 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for turbotwix 0.2.1
File Size Uploaded
turbotwix-0.2.1.tar.gz 29.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for turbotwix 0.2.1
File Interpreter ABI Platform
turbotwix-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 63.2 kB

Release files / turbotwix-0.2.1.tar.gz

Download URL turbotwix-0.2.1.tar.gz
Size 29.9 kB
Tags Source
SHA-256 checksum
How to use checksums
cc5778cd4862ae4a1377ba4a2e58071af0cd8507685e59a3030b98e2e08cb431
BLAKE2b-256 checksum
How to use checksums
6a3f19584a6829fd3ecc8df903321e4d5acb2d9cceffc8ac982e8910c5444599
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / turbotwix-0.2.1-py3-none-any.whl

Download URL turbotwix-0.2.1-py3-none-any.whl
Size 33.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6fbac788f437ab19d9c4437db07613d942193c2afed648364be6bb70f9e2dfe4
BLAKE2b-256 checksum
How to use checksums
8ef532907fddd3c44ffbcbd141272687b4eb8f4776632a27fe322be5e965dc5c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page