Using radiens-core from MATLAB
radiens-core works from MATLAB through MATLAB's built-in Python interface. You call AllegoClient and VidereClient from MATLAB code and get back the same objects a Python script gets; MATLAB converts numbers, strings and arrays at the boundary.
Requirements
- MATLAB R2024b or later.
radiens-coreneeds Python 3.12, and R2024b is the first MATLAB release that supports it. See MathWorks' Python version compatibility table for the versions your release accepts. - A Python 3.12+ installation that MATLAB supports, with
radiens-coreinstalled into it. - A running Radiens desktop application: Allego for
AllegoClient, Videre forVidereClient.
Setup
-
Install
radiens-coreinto the Python that MATLAB will use. A virtual environment keeps it separate from other projects:On Windows the interpreter is
radiens-matlab\Scripts\python.exe. -
Point MATLAB at that interpreter, giving its full path. Run this once per MATLAB session, before any other
py.call; MATLAB can't switch interpreters after it has loaded one. -
Check the install:
Calling the clients
Python calls translate directly:
| Python | MATLAB |
|---|---|
from radiens_core import AllegoClientclient = AllegoClient() |
client = py.radiens_core.AllegoClient(); |
with AllegoClient() as client: |
cleanup = onCleanup(@() client.close()); inside a function |
client.get_signals([2.0, 3.0]) |
client.get_signals([2 3]) |
client.downsample(meta, factor=4) |
client.downsample(meta, factor=int32(4)) |
status.streaming.stream_mode.name |
string(status.streaming.stream_mode.name) |
MATLAB has no with statement. Put onCleanup inside a function rather than a script, so the client closes when the function exits, including on Ctrl-C or an error.
MATLAB numbers are doubles and reach Python as float. Where a parameter is typed int, such as factor above, wrap the value in int32(...). Numeric vectors such as time ranges [start end] and channel lists [0 1 2 3] can be passed as they are. Channel indices are zero-based native indices, as in Python.
Converting results
| Returned Python type | Example | Convert with |
|---|---|---|
| numpy array | sigs.amp |
double(sigs.amp) |
tuple or list of numbers |
sigs.time_range.sec |
cellfun(@double, cell(sigs.time_range.sec)) |
float / int |
meta.time_range.fs |
double(meta.time_range.fs) |
str |
meta.base_name |
string(meta.base_name) |
| enum member | meta.file_type |
string(meta.file_type.name) |
| model object | meta |
read fields with dot syntax: meta.time_range.dur_sec |
Signal arrays are channels × samples, as in Python. Each conversion copies the data, so in a fast streaming loop the effective loop period can be noticeably longer than the poll interval.
A Python exception raised by radiens-core reaches MATLAB as a matlab.exception.PyException, an MException that try/catch handles like any other error. Its message reads Python Error: <exception class>: <text>, and its ExceptionObject property holds the Python exception for telling error types apart.
Examples
Closed-loop signal acquisition
The MATLAB version of the closed-loop example: continuously read only the samples that arrived since the last fetch.
function closed_loop_streaming()
% Closed-loop signal acquisition that continuously fetches only new data.
%
% Tracks the end time of the last successful fetch and requests only the
% samples that have arrived since, polling every pollIntervalSec seconds.
% Interrupt with Ctrl-C to stop.
pollIntervalSec = 0.5;
client = py.radiens_core.AllegoClient();
client.start_streaming();
cleanup = onCleanup(@() stopAndClose(client));
% Let the cache fill briefly before the first fetch
pause(1.0)
% Seed the cursor at the current cache head so we only fetch new data
cacheRange = primaryCacheRange(client);
cursor = cacheRange(2);
fprintf("Starting at cache head: %.3f s, polling every %g s\n", cursor, pollIntervalSec)
fprintf("Press Ctrl-C to stop.\n\n")
while true
pause(pollIntervalSec)
cacheRange = primaryCacheRange(client);
if cacheRange(2) <= cursor
continue % no new data yet
end
% Clamp in case the rolling cache has advanced past our cursor
fetchStart = max(cursor, cacheRange(1));
try
sigs = client.get_signals([fetchStart, cacheRange(2)]);
catch err
fprintf("Fetch error (skipping window): %s\n", err.message)
cursor = cacheRange(2);
continue
end
% --- Process the new chunk here ---
served = toDouble(sigs.time_range.sec);
amp = double(sigs.amp); % channels x samples
if ~isempty(amp)
% get_signals returns wideband by default; remove each channel's DC offset
ac = amp - mean(amp, 2);
fprintf("[%.3f - %.3f s] %d ch x %d samples RMS = %.4f\n", ...
served(1), served(2), size(amp, 1), size(amp, 2), sqrt(mean(ac.^2, "all")))
end
% Advance by what was served, which can be shorter than requested
cursor = served(2);
end
end
function range = primaryCacheRange(client)
status = client.get_status();
range = toDouble(status.streaming.primary_cache_t_range);
end
function values = toDouble(pyTuple)
values = cellfun(@double, cell(pyTuple));
end
function stopAndClose(client)
client.stop_streaming();
client.close();
fprintf("\nStopped.\n")
end
Reading a recording
Link a recorded file in Videre, read one second of four channels and plot it.
function load_recording()
% Link a recorded file in Videre, read one second of four channels and plot it.
% --- CONFIGURATION (Change this to match your local setup) ---
dataPath = "path/to/your/recording_data.xdat";
% --------------------------------------------------------------
client = py.radiens_core.VidereClient();
cleanup = onCleanup(@() client.close());
meta = client.link_data_file(dataPath);
fs = double(meta.time_range.fs);
fprintf("Linked %s: %g Hz, %.2f s\n", string(meta.base_name), fs, double(meta.time_range.dur_sec))
% Seconds 2 to 3 of amplifier channels 0-3 (native indices, zero-based)
sigs = client.get_signals(meta, [2 3], [0 1 2 3]);
amp = double(sigs.amp); % channels x samples
served = cellfun(@double, cell(sigs.time_range.sec));
t = served(1) + (0:size(amp, 2) - 1) / fs;
plot(t, amp')
xlabel("Time (s)")
ylabel("Amplitude")
legend("ch " + string(0:size(amp, 1) - 1))
end
Save each example as a .m file with the same name as its function, then run it from the MATLAB command window, for example closed_loop_streaming.
Troubleshooting
-
pyenvreports the wrong version, or theVersioncan't be changed. MATLAB loaded a Python interpreter earlier in the session. Restart MATLAB and runpyenvfirst. -
MATLAB crashes or reports a library conflict when importing
radiens_core. Run Python in a separate process instead, before any otherpy.call:Calls then carry some extra overhead. To restart that Python process, run
terminate(pyenv). -
Unable to resolve the name 'py.radiens_core.AllegoClient'.radiens-coreisn't installed in the interpreterpyenvreports. Install it there, then restart MATLAB.