Skip to content

Call flywheel-sdk from MATLAB

MATLAB SDK Deprecation Notice

The dedicated Flywheel MATLAB software development kit (SDK) is deprecated and will reach end of life (EOL) on August 30th, 2026. Use the approach described in this guide to call the Flywheel Python SDK directly from MATLAB instead. See Migrate from the MATLAB SDK for a comparison and migration steps.

MATLAB provides built-in support for calling Python libraries using the py. prefix. This allows you to use the Flywheel Python SDK (flywheel-sdk) directly from your MATLAB environment without needing a separate MATLAB-specific SDK.

This guide covers:

  • Installing and configuring the SDK for MATLAB
  • Translating common Python SDK operations into MATLAB syntax
  • Helper utilities to reduce boilerplate
  • Common patterns and performance tips
  • Data type conversions and troubleshooting
  • Migrating from the dedicated MATLAB SDK

Prerequisites

  • MATLAB R2019b or later (R2023a or later recommended)
  • Python 3.8 through 3.11 (3.12 and later have limited MATLAB support)
  • A Python architecture (64-bit or 32-bit) that matches your MATLAB installation

Set up the environment

Step 1: Install the Flywheel Python SDK

Install flywheel-sdk into your Python environment:

pip install flywheel-sdk

For a dedicated environment (recommended), create a virtual environment:

1
2
3
python3 -m venv ~/flywheel-matlab-env
source ~/flywheel-matlab-env/bin/activate  # Windows: Scripts\activate
pip install flywheel-sdk

Step 2: Configure MATLAB to use Python

Check which Python installation MATLAB uses:

pyenv

If Python is not configured or you need a different version:

1
2
3
4
5
% macOS / Linux
pyenv('Version', '~/flywheel-matlab-env/bin/python');

% Windows
pyenv('Version', 'C:\path\to\flywheel-matlab-env\Scripts\python.exe');

Note

Once Python initializes in a MATLAB session, you cannot change versions without restarting MATLAB. Configure pyenv before making any py. calls.

Step 3: Verify the setup

Run a quick import test:

1
2
3
% Test import
fw_module = py.importlib.import_module('flywheel');
disp('flywheel-sdk successfully imported!');

For a comprehensive check that validates your MATLAB version, Python configuration, SDK installation, authentication, and basic operations, download and run the full verification script:

Download verify_matlab_python_setup.m

verify_matlab_python_setup()

The script outputs a pass/fail status for each check with actionable troubleshooting steps for any failures.

Translate Python SDK calls to MATLAB

The following examples show common Python SDK operations and their MATLAB equivalents. MATLAB syntax closely mirrors Python for most SDK calls, with differences primarily in module imports, dictionary construction, and iteration.

Tip

The helper utilities section provides convenience functions that reduce the verbosity of dictionary creation, list conversion, and pagination.

Create a client and authenticate

1
2
3
4
5
6
7
import flywheel

# Using API key
fw = flywheel.Client('your-api-key-here')

# Using saved credentials
fw = flywheel.Client()
% Import module
fw_module = py.importlib.import_module('flywheel');

% Using API key
client = fw_module.Client('your-api-key-here');
fw = py.getattr(client, '_fw'); 

% Using saved credentials (after running `fw login` in terminal)
client = fw_module.Client();
fw = py.getattr(client, '_fw');     

In MATLAB, you first import the module with py.importlib.import_module(). After that, client creation follows the same pattern as Python.

Retrieve user information

1
2
3
user = fw.get_current_user()
print(f"Logged in as: {user.firstname} {user.lastname}")
print(f"Email: {user.email}")
1
2
3
4
user = fw.get_current_user();
fprintf('Logged in as: %s %s\n', ...
    string(user.firstname), string(user.lastname));
fprintf('Email: %s\n', string(user.email));

You can access Python object properties directly with dot notation. Wrap Python strings in string() when displaying them with fprintf.

Look up objects by path

1
2
3
project = fw.lookup('flywheel/my-project')
session = fw.lookup('flywheel/my-project/subject-01/session-01')
print(f"Project: {project.label}")
1
2
3
project = fw.lookup('flywheel/my-project');
session = fw.lookup('flywheel/my-project/subject-01/session-01');
fprintf('Project: %s\n', string(project.label));

Path-based lookups use identical syntax in both languages.

Retrieve objects by ID

1
2
3
project = fw.get_project('project-id-here')
session = fw.get_session('session-id-here')
acquisition = fw.get_acquisition('acquisition-id-here')
1
2
3
project = fw.get_project('project-id-here');
session = fw.get_session('session-id-here');
acquisition = fw.get_acquisition('acquisition-id-here');

Direct retrieval by ID uses identical syntax in both languages.

Update objects

1
2
3
4
5
6
7
8
9
fw.modify_project('project-id', {
    'label': 'Updated Project Name',
    'description': 'New description'
})

fw.modify_session('session-id', {
    'label': 'Updated Session',
    'age': 365
})
% Create Python dict for updates
updates = py.dict(pyargs(...
    'label', 'Updated Project Name', ...
    'description', 'New description'));
fw.modify_project('project-id', updates);

% Use explicit integer types where needed
session_updates = py.dict(pyargs(...
    'label', 'Updated Session', ...
    'age', int32(365)));
fw.modify_session('session-id', session_updates);

MATLAB requires py.dict() to create Python dictionaries and pyargs() for keyword arguments. Use explicit numeric types like int32() for integer values.

Find objects with filters

1
2
3
4
5
projects = fw.projects.find('label=~MRI')
sessions = fw.sessions.find('project.label=~MRI,age>30')

for project in projects:
    print(f"Found: {project.label}")
1
2
3
4
5
6
7
8
9
projects = fw.projects.find('label=~MRI');
sessions = fw.sessions.find('project.label=~MRI,age>30');

% Convert Python generator to list for iteration
project_list = cell(py.list(projects));
for i = 1:length(project_list)
    proj = project_list{i};
    fprintf('Found: %s\n', string(proj.label));
end

Filter syntax is identical. To iterate over results in MATLAB, convert the Python generator to a list with py.list(), then use cell array indexing {}.

Iterate over large result sets

1
2
3
all_subjects = fw.subjects.iter()
for subject in all_subjects:
    print(f"Subject: {subject.label}")
1
2
3
4
5
6
7
8
all_subjects = fw.subjects.iter();

% Convert to list (loads all into memory)
subject_list = cell(py.list(all_subjects));
for i = 1:length(subject_list)
    subject = subject_list{i};
    fprintf('Subject: %s\n', string(subject.label));
end

MATLAB does not handle Python generators natively. Converting with py.list() loads all results into memory.

Tip

For very large datasets, use manual pagination to avoid loading everything into memory at once:

page_size = 100;
offset = 0;
all_items = {};

while true
    page = fw.subjects.find(pyargs( ...
        'limit', int32(page_size), 'skip', int32(offset)));
    page_list = cell(py.list(page));

    if isempty(page_list)
        break;
    end

    all_items = [all_items; page_list];
    offset = offset + page_size;

    if length(page_list) < page_size
        break;
    end
end

Upload files

1
2
3
4
5
6
7
8
acquisition = fw.get_acquisition('acquisition-id')
acquisition.upload_file('/path/to/file.nii.gz')

# Upload with metadata
acquisition.upload_file(
    '/path/to/file.dcm',
    metadata={'Modality': 'MR', 'SliceThickness': 3.0}
)
1
2
3
4
5
6
7
8
9
acquisition = fw.get_acquisition('acquisition-id');
acquisition.upload_file('/path/to/file.nii.gz');

% Upload with metadata
metadata = py.dict(pyargs(...
    'Modality', 'MR', ...
    'SliceThickness', 3.0));
acquisition.upload_file('/path/to/file.dcm', ...
    pyargs('metadata', metadata));

Simple uploads use identical syntax. Adding metadata requires constructing a py.dict() object.

Download files

1
2
3
4
5
6
acquisition = fw.get_acquisition('acquisition-id')

for file_info in acquisition.files:
    print(f"File: {file_info.name}, Size: {file_info.size}")

acquisition.download_file('scan.nii.gz', '/local/path/scan.nii.gz')
acquisition = fw.get_acquisition('acquisition-id');

% List files (convert to iterate)
files = py.list(acquisition.files);
for i = 1:length(files)
    file_info = files{i};
    fprintf('File: %s, Size: %d\n', ...
        string(file_info.name), int64(file_info.size));
end

% Download a specific file
acquisition.download_file('scan.nii.gz', '/local/path/scan.nii.gz');

File iteration requires list conversion. The download call itself uses identical syntax.

Handle exceptions

1
2
3
4
5
6
7
8
from flywheel import ApiException

try:
    project = fw.get_project('nonexistent-id')
except ApiException as e:
    print(f"API Error: {e.status} - {e.reason}")
except Exception as e:
    print(f"Unexpected error: {str(e)}")
try
    project = fw.get_project('nonexistent-id');
catch ME
    if contains(ME.identifier, 'Python')
        fprintf('Python error: %s\n', ME.message);

        % Check for specific HTTP status codes
        if contains(ME.message, '404')
            fprintf('Object not found\n');
        elseif contains(ME.message, '401')
            fprintf('Authentication failed\n');
        end
    else
        fprintf('MATLAB error: %s\n', ME.message);
    end
end

Python exceptions are wrapped in MATLAB's MException object. You cannot catch specific Python exception types by name. Instead, inspect the error message for HTTP status codes.

Helper utilities

The flywheel_matlab_helpers class provides convenience functions that reduce boilerplate when working with the Python SDK from MATLAB.

Download flywheel_matlab_helpers.m

Add the file to your MATLAB path, then use its static methods:

Method Purpose
create_client(api_key) Initialize a Flywheel client (one-liner)
to_py_dict(key1, val1, ...) Create a Python dictionary from key-value pairs
to_py_list(array) Convert a MATLAB array or cell array to a Python list
from_py_list(py_list) Convert a Python list or generator to a MATLAB cell array
safe_string(py_obj) Convert a Python object to a string, returning '' for None
iter_all(iterator) Consume a Python iterator into a cell array
paginated_find(container, filter, page, size) Fetch one page of results
find_all_paginated(container, filter, size) Fetch all results using automatic pagination
try_get_property(obj, name, default) Access a property with a fallback for missing or None values
setup_check() Print diagnostic information about the environment

Create clients and convert types

The helpers simplify the most verbose MATLAB-Python patterns:

1
2
3
4
5
6
7
8
9
% Without helpers
fw_module = py.importlib.import_module('flywheel');
fw = fw_module.Client('your-api-key');
updates = py.dict(pyargs('label', 'New Name', 'description', 'Updated'));

% With helpers
fw = flywheel_matlab_helpers.create_client('your-api-key');
updates = flywheel_matlab_helpers.to_py_dict( ...
    'label', 'New Name', 'description', 'Updated');

Paginate large result sets

Use find_all_paginated to fetch all matching items without loading everything into memory at once:

1
2
3
4
5
6
7
% Fetch all MRI sessions in pages of 50
sessions = flywheel_matlab_helpers.find_all_paginated( ...
    fw.sessions, 'project.label=~MRI', 50);

% Or fetch a single page
page1 = flywheel_matlab_helpers.paginated_find( ...
    fw.sessions, 'project.label=~MRI', 1, 100);

Access properties safely

Python objects may return None for optional fields. Use try_get_property and safe_string to avoid errors:

1
2
3
4
5
6
7
8
9
project = fw.get_project('project-id');

% Safe property access with a default value
age = flywheel_matlab_helpers.try_get_property(session, 'age', 0);
desc = flywheel_matlab_helpers.try_get_property( ...
    project, 'description', 'No description');

% Safe string conversion (returns '' for None)
label = flywheel_matlab_helpers.safe_string(project.label);

Common patterns

Iterate and process containers

A common workflow is to iterate over containers and process each one:

1
2
3
4
5
6
7
8
9
projects = cell(py.list(fw.projects.find('')));

for i = 1:length(projects)
    project = projects{i};
    fprintf('Project: %s (ID: %s)\n', ...
        string(project.label), string(project.id));

    % Process project...
end

Apply batch updates

To update multiple containers in a loop, construct the update dictionary once per iteration and pass the container ID as a string:

1
2
3
4
5
6
7
8
9
sessions = cell(py.list( ...
    fw.sessions.find('project.label=MyProject')));

for i = 1:length(sessions)
    session = sessions{i};
    updates = py.dict(pyargs('age', int32(365)));
    fw.modify_session(string(session.id), updates);
    fprintf('Updated session %d/%d\n', i, length(sessions));
end

Handle missing or null properties

Python SDK objects may return None for optional fields. Check before converting:

project = fw.get_project('project-id');

% Manual approach
if ~isempty(project.description) ...
        && ~isa(project.description, 'py.NoneType')
    desc = string(project.description);
else
    desc = 'No description';
end

% Or use the helper
desc = flywheel_matlab_helpers.safe_string(project.description);
if isempty(desc)
    desc = 'No description';
end

Build nested metadata

Some application programming interface (API) calls require nested dictionaries. Build them from the inside out:

1
2
3
4
% Python equivalent: {'meta': {'tags': ['a', 'b'], 'source': 'scanner'}}
tags = py.list({'a', 'b'});
meta = py.dict(pyargs('tags', tags, 'source', 'scanner'));
data = py.dict(pyargs('meta', meta));

Performance tips

  1. Import the module once and reuse the handle. Calling py.importlib.import_module('flywheel') on every operation adds unnecessary overhead.

    1
    2
    3
    % Do this once at the top of your script
    fw_module = py.importlib.import_module('flywheel');
    fw = fw_module.Client('your-api-key');
    
  2. Convert result lists once, then iterate. Avoid calling py.list() repeatedly on the same data.

    1
    2
    3
    4
    5
    6
    7
    % Convert once
    projects = cell(py.list(fw.projects.find('')));
    
    % Reuse the converted list
    for i = 1:length(projects)
        % Process projects{i}
    end
    
  3. Use pagination instead of iter() for large datasets. Loading thousands of items with py.list(fw.subjects.iter()) consumes significant memory. Use find() with limit and skip parameters, or the find_all_paginated helper.

  4. Pre-convert types outside loops. If you apply the same update to many containers, build the dictionary once:

    1
    2
    3
    4
    5
    updates = py.dict(pyargs('status', 'reviewed'));
    
    for i = 1:length(sessions)
        fw.modify_session(string(sessions{i}.id), updates);
    end
    
  5. Batch your queries. Retrieve all containers you need in a single find() call, then process locally. Avoid calling get_session() inside a loop when fw.sessions.find() can return all results at once.

Data type conversion reference

Automatic conversions

Python Type MATLAB Type Notes
str char / string Bidirectional
int double Python to MATLAB automatic
float double Bidirectional
bool logical Bidirectional
None py.NoneType Check with isa(obj, 'py.NoneType')

Manual conversions

Conversion Syntax
MATLAB to Python dict py.dict(pyargs('key', 'value'))
MATLAB cell to Python list py.list({'a', 'b', 'c'})
MATLAB array to Python list py.list(num2cell([1, 2, 3]))
Python list to MATLAB cell cell(py.list(python_obj))
MATLAB to Python integer int32(365) or int64(1024)

Problematic conversions

Some conversions require special attention:

  • Python generators have no MATLAB equivalent. You must convert them to a list with py.list(), which loads the entire result set into memory.
  • Complex nested structures are verbose to construct. Build inner dictionaries first, then wrap them in outer ones (see Build nested metadata).
  • MATLAB numeric arrays do not convert to Python lists automatically. Use num2cell() first: py.list(num2cell([1, 2, 3])).

Troubleshoot common issues

ModuleNotFoundError: No module named 'flywheel'

Verify that flywheel-sdk is installed in the Python environment MATLAB uses:

pyenv
system('pip list | grep flywheel')

Unable to resolve the name py.flywheel.Client

Restart MATLAB after installing flywheel-sdk. MATLAB caches the Python module list at startup and does not detect newly installed packages.

Python version not supported

Use Python 3.8 through 3.11 for best compatibility. Python 3.12 and later have limited MATLAB support. Check your version:

pyenv

If you need to switch versions, restart MATLAB and configure pyenv before any py. calls:

pyenv('Version', '/path/to/correct/python');

Type conversion errors

Use explicit types for integers:

1
2
3
age = int32(365);     % 32-bit integer
size = int64(1024);   % 64-bit integer
weight = 75.5;        % MATLAB doubles work as Python floats

Authentication failed

1
2
3
4
5
6
7
8
% Option 1: Provide API key directly
fw = fw_module.Client('your-api-key');

% Option 2: Log in through the command-line interface (CLI) first,
% then use saved credentials.
% In terminal: fw login
% In MATLAB:
fw = fw_module.Client();

Migrate from the MATLAB SDK

The dedicated Flywheel MATLAB SDK is deprecated. This section helps you transition existing scripts to the Python SDK interop approach described in this guide.

Syntax comparison

The following table compares common operations between the old MATLAB SDK and the new Python SDK approach:

Operation MATLAB SDK (deprecated) Python SDK via MATLAB
Create client fw = flywheel.Flywheel(key) fw = py.getattr(py.importlib.import_module('flywheel').Client(key), '_fw')
Read a property project.label string(project.label)
Update an object project.update(...) fw.modify_project(id, py.dict(...))
Create a dictionary struct('key', value) py.dict(pyargs('key', value))
Iterate over results Direct for loop cell(py.list(...)) then index with {}
Handle exceptions Native MATLAB exceptions Wrapped in MException, parse message strings

What changes

  • Import pattern: You import the Python module with py.importlib.import_module('flywheel') instead of calling MATLAB-native constructors.
  • Dictionary construction: Replace MATLAB structs with py.dict(pyargs(...)) or the to_py_dict helper.
  • List iteration: Convert Python generators to lists before iterating with py.list().
  • String display: Wrap Python strings in string() when passing them to fprintf or other MATLAB functions.
  • Exception handling: Catch MException and inspect the message for HTTP status codes instead of catching typed exceptions.

Tip

Download flywheel_matlab_helpers.m to reduce migration effort. The helper methods closely match the ergonomics of the old MATLAB SDK for common operations like client creation, dictionary construction, and pagination.

Resources