CUDA‑accelerated real‑time plane‑wave beamforming with MATLAB & Python bindings
EchoFrame delivers high‑throughput Fourier‑domain beamforming and Power‑Doppler (PDI) ultrasound pipelines, fully written in C++17 / CUDA and wrapped for both MATLAB (mex) and Python (pybind11).
- Real‑time performance (> 10 kFPS on RTX 40‑series)
- Self‑contained build: CMake ≥ 3.21 (used to build the project) + a recent CUDA toolkit are all you need
Prebuilt binaries: available from GitHub Releases.
| Versions | CUDA 12.8 | CUDA 12.9 | CUDA 13.1 |
|---|---|---|---|
| Matlab 2022a | ✅ | ✅ | ✅ |
| Matlab 2024a | ✅ | ✅ | ✅ |
Questions? Open an issue or e‑mail a.demi@erasmusmc.nl.
| Component | Minimum | Notes |
|---|---|---|
| EchoFrame repository | Latest version | You can download the zip of the repository or use git clone |
| NVIDIA GPU | SM 6.1 (Pascal) | default build targets 61 75 86 89 90 |
| CUDA® Toolkit | 12.4 | nvcc must be in PATH, CUDA_PATH must be set |
| CMake (optional) | 3.21 | CUDA language + C++ 17 |
| MATLAB | R2022a (due to Verasonics) | for the echoframe_mex gateway |
| Signal Processing Toolbox | — | required by the imaging setup (gausswin, tukeywin) |
| Parallel Computing Toolbox (optional) | — | only as a gpuDevice fallback in check_gpu_memory_fit when nvidia-smi is not on PATH |
| Python (optional) | 3.10 | for the echoframe py module |
| Visual Studio (optional) | 2022 | to build from source (only Visual Studio 2022 supported) |
ℹ️ NOTE: EchoFrame can be used and compiled with newer MATLAB versions (tested up to R2025a), the R2022a limitation applies only to use with Verasonics Vantage Systems. The most recent version of Vantage works with R2024a.
If you want to get straight into using EchoFrame (and you have the appropriate prerequisites installed), you can head to Plug and Play. If you want to edit EchoFrame's core behaviour, you will have to build it from source at Building from scratch.
We offer a range of pre-built binaries for EchoFrame. This means you can simply copy and paste these files into your EchoFrame repository and you will be able to run the Matlab and Python scripts without needing to build it yourself.
Download the matching build_CUDA_<version>_MATLAB_<version>.zip from GitHub Releases. Each asset contains a compressed build folder.
Once you have the EchoFrame repository on your local machine, unzip the asset, navigate to EchoFrame\echoframe\cpp\src, and paste the uncompressed build folder inside src. After that, you should be good to go.
You can then explore how to use EchoFrame.
The CLI module is the only target that needs third-party libraries (Matio, HDF5 and ZLIB). The Python module and the MEX gateway link none of them, so if you are not building the CLI you can skip ahead.
Those libraries are declared in echoframe/cpp/src/vcpkg.json and installed automatically at
configure time, so there is no vcpkg install step to run and no version to keep in sync
by hand.
Windows users: Visual Studio 2022 Community already ships with MSVC v143, CMake and Ninja.
All the CLI build needs is a vcpkg checkout and VCPKG_ROOT pointing at it:
# one-time setup of vcpkg if you haven't already
git clone https://github.com/Microsoft/vcpkg C:\vcpkg
C:\vcpkg\bootstrap-vcpkg.bat
# tell the build where it lives (persists across sessions)
setx VCPKG_ROOT C:\vcpkgOpen a new terminal after setx so VCPKG_ROOT is visible, then configure as usual. The
first CLI configure builds Matio and HDF5 and takes a few minutes; every configure after
that is immediate.
The location is up to you -- nothing in the build files refers to C:\vcpkg, so moving the
checkout only means updating VCPKG_ROOT.
Not building the CLI? Then vcpkg is not needed at all. Configure with
-DEF_BUILD_CLI=OFFand the dependency step is skipped entirely. This is what the Python wheel does, which is whypip installneeds nothing but CUDA and a compiler.
During the setup for this application, you may run into issues with the dependencies. Before going to the next section, it is important to make sure that everything is setup properly.
EchoFrame is setup to work with Visual Studio 2022. If you are planning to build EchoFrame yourself instead of following Plug and Play, then you either need to use an existing Visual Studio 2022 instance, get an installer from the official website (you need an active Visual Studio Subscription to access the 2022 version, as the official latest version is 2026), or you can ask us for a copy of the 2022 installer.
You should make sure you have installed Visual Studio 2022 with the optional "Desktop Development with C++" kit selected.
Windows limits paths to 260 characters unless long paths are enabled, and the Visual Studio generator writes deeply nested intermediate paths. If the repository sits far down the filesystem, configuring can fail with:
error MSB4184: ... exceeds the OS max path limit. The fully qualified file name must be
less than 260 characters.
Any one of these fixes it:
- Clone somewhere short, e.g.
C:\dev\EchoFrame. - Enable long paths: set
LongPathsEnabledto1underHKLM\SYSTEM\CurrentControlSet\Control\FileSystem(needs a reboot). - Use the Ninja generator (
-G Ninja), which nests far less. Note Ninja needs a "x64 Native Tools VS 2022" prompt socl.exeis onPATH; the Visual Studio generator arranges that for you.
This affects the build tree only. vcpkg keeps its own deep buildtrees under VCPKG_ROOT,
so a short VCPKG_ROOT (like C:\vcpkg) stays safe regardless of where the repo lives.
CUDA should only be installed after you have setup Visual Studio.
It is important to make sure that your CUDA_PATH variable is set correctly.
On Windows, you can use the GUI (Graphical User Interface):
- Press Win + X and select System (or go to Control Panel > System).
- Click on Advanced system settings on the left side.
- In the System Properties window, click on Environment Variables.
- In the System variables section:
- Look for the
CUDA_PATHvariable. - Set the variable to your desired CUDA version, your path should look something like "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.9".
- Click OK to confirm. Any changes should take effect on a new instance of any terminal window (the variable change will not take effect on an open terminal instance, so close any open ones first).
- Look for the
If you have multiple CUDA versions installed, make sure CMake, nvcc, and Visual Studio BuildCustomizations resolve to the same toolkit version. By default, this folder will be at:
C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\BuildCustomizations.
Inside here you will find files named CUDA MV.mv.props, CUDA MV.mv.targets, CUDA MV.mv.xml, and CUDA MV.mv.Version.props where MV is the major version of CUDA such as 12 or 13, and mv is the minor version.
You should only keep the files of the CUDA version you are planning to use to build the project. You can temporarily rename or move away the files from the other versions while building, and after you are finished you can move them back or rename them to their original names.
You can also force the toolkit version in CMake to avoid mismatches:
cmake -S echoframe/cpp/src -B build -G "Visual Studio 17 2022" -A x64 -T cuda="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v12.8" -DCUDAToolkit_ROOT="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v12.8"If you see nvcc fatal : Unsupported gpu architecture 'compute_52', your selected toolkit may not support that architecture (for example, CUDA 13.x). Set an explicit supported architecture, for example:
-DCMAKE_CUDA_ARCHITECTURES=61or:
-DCMAKE_CUDA_ARCHITECTURES=nativeℹ️ NOTE: You can find a clear automated batch script build_echoframe.bat in the build_scripts/ folder, which might help with understanding how to avoid any issues with building. The prebuilt binaries it produces are built without -DENABLE_CUDA_TIMING, so they do not include the per-frame CUDA timing output. Run it from a terminal with administrator privileges so everything works reliably.
Before continuing, make sure that the submodules included in the project are properly initialized.
# make sure you are in the EchoFrame directory
git submodule init
git submodule updateYou can continue with the rest of the build instructions if you have no errors. Below you can find a set of commands to setup the project for both Linux and Windows.
The commands use an echoframe/cpp/src/build/ directory, and then configure and build the project using CMake.
In order to compile the CLI and the complete Echoframe library use:
# from a "x64 Native Tools VS 2022" prompt or Powershell
cd EchoFrame
#To choose between different versions of Matlab, you can change the -DMATLAB_VERSION variable. For Matlab 2022a the version is 9.12 and for Matlab 2024a the version is 24.1. This command is building with Matlab 2024a.
#If you have multiple versions of Visual Studio installed on your machine, you have to force Visual Studio 2022:
#The vcpkg toolchain is picked up from VCPKG_ROOT automatically, and the CLI's dependencies
#are installed from vcpkg.json at configure time. No toolchain or prefix path to pass here.
cmake -S echoframe/cpp/src -B echoframe/cpp/src/build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release -DMATLAB_VERSION="24.1" -DEF_BUILD_CLI=ON -DEF_BUILD_MEX=ON -DEF_BUILD_PYTHON=ON
#Build using all available CPU cores (MSBuild /m). Use /m:<N> to cap workers.
cmake --build echoframe/cpp/src/build --config Release -- /mℹ️ NOTE: If you want to build the complete library, there is no need to define EF_BUILD_CLI for Python, MEX and CLI. They are set to ON by default, the above is just to demonstrate how they would be used.
Alternatively, configure and build from the bundled preset, which applies the same flags and
pins the x64-windows-static-md vcpkg triplet:
# from a "x64 Native Tools VS 2022" prompt or Powershell
cd EchoFrame/echoframe/cpp/src
cmake --preset default # configure
cmake --build --preset default # buildCompile only the MEX and Python support (no CLI), with Visual Studio 2022 being the only version you have installed:
# from a "x64 Native Tools VS 2022" prompt or Powershell
# make sure you are in the EchoFrame directory
# With -DEF_BUILD_CLI=OFF nothing here needs vcpkg at all -- VCPKG_ROOT is not even read.
cmake -S echoframe/cpp/src -B echoframe/cpp/src/build -DCMAKE_BUILD_TYPE=Release -DMATLAB_VERSION="24.1" -DEF_BUILD_CLI=OFF -DEF_BUILD_MEX=ON -DEF_BUILD_PYTHON=ON
cmake --build echoframe/cpp/src/build --config ReleaseBoth commands generate:
echoframe/cpp/src/build/Release/echoframe_core.<lib|dll|so>– static/shared back‑endechoframe/cpp/src/build/Release/echoframe_mex.*– MATLAB gateway (if enabled)echoframe/cpp/src/build/Release/storage.*– MATLAB RF-storage gateway (if enabled)echoframe/cpp/src/build/Release/echoframe.*– Python extension module (if enabled)
This is the directory the MATLAB scripts load the MEX from, so nothing has to be copied afterwards.
sudo so everything works reliably.
# make sure you are in the EchoFrame directory
cmake -S echoframe/cpp/src -B build \
-DCMAKE_BUILD_TYPE=Release \
-DEF_BUILD_MEX=ON \
-DEF_BUILD_PYTHON=ON \
-DMatlab_ROOT_DIR="$HOME/MATLAB/R2024a" \
-DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++ \
-DCMAKE_CUDA_ARCHITECTURES="75;80;86;89;90"
cmake --build build -j$(nproc)This single build now places both MATLAB MEX files in the same folder:
build/echoframe_mex.mexa64build/storage.mexa64
This section is meant to clarify what changes you can make to the CMake build commands to customise what is built.
(A guide on how to setup the build can be found on the Quick Start section.)
| CMake option | Default | Purpose |
|---|---|---|
EF_BUILD_MEX |
ON | build the MATLAB echoframe_mex and storage MEX wrappers |
EF_BUILD_PYTHON |
ON | build the Python echoframe module |
EF_BUILD_CLI |
ON | build the EchoFrame cli (command line) module |
ENABLE_CUDA_TIMING |
OFF | print per-frame CUDA kernel timings from process() to the console |
CMAKE_CUDA_ARCHITECTURES |
set from the CUDA version: 61;75;86;89;90 on CUDA 12.4+, 75;80;86;89;90;100;103;121 on CUDA 13+ |
compute capability list. CMakeLists.txt sets it after project(), so a value passed on the command line is replaced — edit the list there to narrow it |
Example: disabling Python in Linux:
cd EchoFrame
cmake -S echoframe/cpp/src -B build -DEF_BUILD_PYTHON=OFFIn addition, we also provide a pyproject.toml to build the Python EF wheel binding, more information can be found at Python Setup.
Run logo simulation snippet matlab code echoframe/matlab/examples/logo_simulation/logo_simulation.m script.
~/.bashrc
export ECHOFRAME_PATH=$HOME/EchoFrame/
export PATH="$PATH:$ECHOFRAME_PATH"Then run the following:
source ~/.bashrcAfter creating a fresh environment (e.g., see command below):
python -m venv "path_to_env"Build the Python wheel:
cd EchoFrame/echoframe/cpp/src
pip install -e .This needs only CUDA and a host compiler. The wheel builds the Python extension alone, so
it does not use vcpkg, Matio, HDF5 or ZLIB, and VCPKG_ROOT does not have to be set.
After editing the C++ sources, rebuild incrementally with --no-build-isolation
(install the build backend once, then reuse the persistent build_py/ tree so only
changed files recompile):
pip install scikit-build-core pybind11 ninja
pip install -e . --no-build-isolationAlso, install the following packages used by the echoframe/python/examples/process_echoframe_offline.py:
pip install numpy
pip install h5py
pip install matplotlib
pip install scipyIn order to verify the succesful installation try the following in a Python console which should return no errors:
import echoframe as efEchoFrame beamforms faster than most drives can absorb. When RF is saved, the drive decides how long a recording can run.
Each saved stream costs bytes per frame, and the frame period is
nTransmissions * nRepeats / txRate:
RF per frame = nSamples * nChannels * nTransmissions * nRepeats * 2 (int16)
BF per frame = nz * nx * nRepeats * 8 (complex single)
PDI per frame = nz * nx * 4
required rate = (sum of saved streams) / frame period
report_storage_demand prints this before an acquisition starts, as
SUSTAINED WRITE NEEDED. Your drive has to beat it continuously.
Use diskspd with the access pattern the storage layer uses: unbuffered, write-through, a shallow queue and large blocks. You can change the -d600 variable (600 seconds) to whatever matches your desired acquisition period.
diskspd -c1500G -d600 -b1M -o3 -t1 -w100 -Suw -L -D D:\diskspd_probe.dat
Remove-Item D:\diskspd_probe.dat| Parameter | What it does |
|---|---|
-c1500G |
Creates the test file at 1500 GiB. Set it larger than the run will write, otherwise the test wraps back onto blocks it has already written and measures a rewrite instead of new data. |
-d600 |
Runs for 600 seconds, after which the file is discarded. Match it to the recording length you intend to make. |
-b1M |
Writes 1 MiB per operation, the same order of block size the storage layer issues. |
-o3 |
Keeps 3 writes outstanding at once, the queue depth EchoFrame uses. |
-t1 |
Uses one thread, as each stream is written from one thread. |
-w100 |
100% writes, no reads. |
-Suw |
u turns off OS buffering (FILE_FLAG_NO_BUFFERING), w writes through the drive's own cache (FILE_FLAG_WRITE_THROUGH). These match how EchoFrame opens its files. Without them the OS absorbs the writes into RAM and reports a rate the drive cannot hold. |
-L |
Records the latency distribution, including the worst single write. |
-D |
Records throughput per interval instead of only an average, so a rate that falls away during the run is visible. |
D:\diskspd_probe.dat |
The file to write. Put it on the drive you will be recording to. |
Access is sequential by default, which is what EchoFrame does, so do not add -r.
Read the mean MiB/s as the rate your drive can hold. Add -Rxml for the full
per-interval breakdown, which shows whether the drive starts fast and settles lower;
size your configuration against the settled figure, not the opening one.
Compare the rate your configuration requires against the rate you measured, and choose
a configuration that matches the drive you will record to. benchmark_storage in
echoframe/matlab/benchmarks/ answers the same
question from inside EchoFrame: its endurance mode writes every stream the
acquisition writes and reports how many frames pass before the drive falls behind. It
takes longer than the diskspd probe but needs no extra tool.
RF dominates the write. For a regular config in our setup:
| Stream | Share of the write |
|---|---|
| RF | 83% |
| BF | 17% |
| PDI | under 1% |
| Total | 100% |
Dropping BF removes a sixth of the load; only dropping RF removes five sixths, so plan carefully.
If your drive cannot carry RF at your frame rate, in order of what costs least:
| Change | Effect |
|---|---|
Reduce nSamples, channels, angles or ensemble length |
Reduces RF in proportion, and the reconstruction with it |
| Shorten the recording | A recording short enough to finish before the drive falls behind still completes. The per-interval output tells you how long that is |
| Stripe across several NVMe drives, or fit an SSD rated for that sustained write | The only option that keeps full RF at full rate |
| Folder | What it shows |
|---|---|
| echoframe/matlab/examples/logo_simulation/ | end-to-end offline example, no hardware or stored data needed |
| echoframe/matlab/examples/process_echoframe_data/ | replay stored RF or BF data offline from MATLAB |
| echoframe/matlab/examples/verasonics/ | live Verasonics acquisition, and processing a saved workspace |
| echoframe/python/examples/ | the same offline replay from Python |
| echoframe/matlab/tests/reference/ | Fourier beamforming in plain MATLAB, as a reference for the CUDA path |
| echoframe/matlab/benchmarks/ | throughput benchmarks for processing and for storage |
Each folder has its own README with the details.
-
echoframe/
- cpp/: the C/C++/CUDA core in
echoframe/cpp/src/, including the MEX gateway, the Python bindings, the CLI, and submodules inlibs/. - matlab/: the MATLAB side, split by role.
- core/: the library -
imaging/,storage/(writes recordings),reading/(reads them back),verasonics/, andsetup/(path setup). - tests/: the
verify_*harnesses, plusdata/(shared test-data generators) andreference/(the plain-MATLAB beamformer used as ground truth). - benchmarks/: throughput benchmarks and the GPU-fit pre-flight check.
- examples/: runnable examples. See echoframe/matlab/examples/README.md.
- core/: the library -
- python/: core/ helper modules for reading saved data, and examples/ for the Python replay path.
- cpp/: the C/C++/CUDA core in
-
documentation/: Doxygen configuration and generated output.
-
build_scripts/: developer helper scripts — the Windows build, vcpkg setup, and the build-verification harness. See build_scripts/README.md.
-
docker/: the Ubuntu/CUDA development image.
-
justfile: task runner recipes. Stays at the repository root so
justcan find it.
- Fork → feature branch → PR
- Run
clang‑format -ion C++ sources (style file in repo)
EchoFrame is released under the GNU Lesser General Public License v3.0.
See LICENSE for the LGPL text. The LGPLv3 applies the terms of the
GNU General Public License v3.0 with additional permissions, so a copy of the
GPLv3 is included as GPLv3.txt.
© 2022‑2026 EchoFrame Contributors