Using the Flywheel Python SDK with R

Introduction

R users can interact with the Flywheel Python SDK Using R via the reticulate library.

Installation

  1. Download and install Python 3. (We recommend 3.7 or later for best results)

  2. Install the latest Flywheel SDK package using pip:

pip install flywheel-sdk
  1. Install reticulate in R:

install.packages("reticulate")

Flywheel SDK setup in R

The following will need to be added to your R script in order to use the Flywheel Python SDK with reticulate

# Load reticulate
library(reticulate)
# Configure reticulate to use python3
use_python(Sys.which("python3"))
# Import flywheel python package
flywheel <- import('flywheel')

API Key

The SDK requires an API key. You can find and generate your key on the Flywheel profile page. It will look like this:

_images/api-key.png

Making API Calls

In order to make API calls, you will need to create an instance of the Flywheel client:

# Create client
fw <- flywheel$Client("my-key")

Once you have a client instance, you can interact with the system. For instance, you could get information about yourself:

self <- fw$get_current_user()
sprintf("I am %s %s", self$firstname, self$lastname)

Using CLI Credentials

If you’ve logged in using the CLI, you can create a client instance without using an API key. This is useful when sharing SDK scripts for others to use.

# Create client, using CLI credentials
fw <- flywheel$Client()

Finding Objects

With the exception of Groups, all containers and objects within Flywheel are referenced using Unique IDs. Groups are the only object that have a human-readable id (e.g. flywheel).

Finding an Object when you are only familiar with the label can be difficult. One method that may help is the resolve() method.

Resolve takes a path (by label) to an Object in the system, and if found, returns the full path to that Object, along with children. For example, to find the project labeled Anxiety Study that belongs to the flywheel group, I would call resolve with: 'flywheel/Anxiety Study':

# Resolve project by id
result <- fw$resolve('flywheel/Anxiety Study')

# Extract the resolved project id
project <- result$path[-1]

# Print the ids and labels of the path elements
for (item in result$path) {
    print_string <- sprintf('%s: %s', item$label, item$id)
    print(print_string)
}

In a similar vein to resolve, lookup() will directly resolve a container by path. For example:

# Lookup project by id
project <- fw$lookup('flywheel/Anxiety Study')

Finally, if the ID of the Object is known, then it can be retrieved directly using the flywheel.flywheel.Flywheel.get() method.

# Get session by id
session <- fw$get('5d2761363289d60026e8aa19')

Working with Objects

Most Objects in the Flywheel SDK provide methods for common operations. For example, to update properties on an object, you can simply call the update method, passing in a dictionary or key value pairs:

# Update a project's label
project$update(label='New Project Label')

# Update a subject's type and sex
subject$update(dict('type'='human', 'sex'='female'))

It’s important to note that calling update will not update your local copy of the object! However, you can quickly refresh an object by calling reload:

# Reload a session
session <- session$reload()

Working with Finders

Another way to find objects is via Finders provided at the top level, and on objects. Finders allow locating objects via arbitrary filtering. Depending on which version of a finder method you call, you can retrieve all matching objects, or the first matching object. Finally, if you want to walk over a large number of objects, finders support iteration.

Filter Syntax

Filter strings are specified as the first argument to a find function. Multiple filters can be separated by commas. Filtering can generally be done on any property on an object, using dotted notation for sub-properties. Type conversion happens automatically. To treat a value as a string, wrap it in quotes: e.g. label="My Project".

Types supported are:

  • Dates in the format YYYY-MM-DD

  • Timestamps in the format YYYY-MM-DDTHH:mm:ss

  • Numeric values (e.g. 42 or 15.7)

  • The literal value null

Operations supported are:

  • Comparison operators: <, <=, =, !=, >=, >

  • Regular expression match: =~

Sorting

In addition to filtering, sorting is supported in the sytax: <fieldname>:<ordering>. Where fieldname can be any property, and ordering is either asc or desc for ascending or descending order, respectively.

Examples

# Retrieve all projects (with a default limit)
all_projects <- fw$projects()

# Find the first project with a label of 'My Project'
project <- fw$projects$find_first('label=My Project')

# Find all sessions in project created after 2018-10-31
sessions <- project$sessions$find('created>2018-10-31')

# Iterate over all failed jobs
for (job in iterate(fw$jobs$iter_find('state=failed'))){
    print_string <- sprintf('Job: %s, Gear: %s', job$id, job$gear_info$name)
    print(print_string)
}

# Iterate over all sessions belonging to project
for (session in iterate(project$sessions$iter())){
    print(session$label)
}

Dealing with Files

Often times you’ll find yourself wanting to upload or download file data to one of Flywheel’s containers. When uploading, you can either specify the path to the input file, or you can specify some in-memory data to upload using the FileSpec object.

# Upload the file at /tmp/hello.txt
project$upload_file('/tmp/hello.txt')

# Upload the data 'Hello World!'
file_spec <- flywheel$FileSpec('hello.txt', 'Hello World!\n', 'text/plain')
project$upload_file(file_spec)

# Some endpoints allow multiple file uploads:
analysis$upload_output(list('/tmp/hello1.txt', '/tmp/hello2.txt'))

When downloading, you specify the destination file, or you can download directly to memory

# Download file to /tmp/hello.txt
project$download_file('hello.txt', '/tmp/hello.txt')

# Download file contents directly to memory
data <- project$read_file('hello.txt')

Working with Zip Members

Occasionally you may want to see the contents of a zip file, and possibly download a single member without downloading the entire zipfile. There are a few operations provided to enable this. For example:

# Get information about a zip file
zip_info <- acquisition$get_file_zip_info('my-archive.zip')

# Download the first zip entry to /tmp/{entry_name}
entry_name <- zip_info$members[[1]]$path
out_path <- file.path('/tmp', entry_name)
acquisition$download_file_zip_member('my-archive.zip', entry_name, out_path)

# Read the "readme.txt" zip entry directly to memory
 zip_data <- acquisition$read_file_zip_member('my-archive.zip', 'readme.txt')

Handling Exceptions

When an error is encountered while accessing an endpoint, an flywheel.rest.ApiException is thrown. The ApiException will typically have a status which is the HTTP Status Code (e.g. 404) and a reason (e.g. Not Found).

For example:

tryCatch(
    {
        project <- fw$get_project('NON_EXISTENT_ID')
    },
    error = function(e) {
        print(e$message)
    }
)

Tips for Translating the Python SDK to R

While this document provides a good start to using the Flywheel Python SDK with R, you may wish to translate other examples in the Python SDK documentation to R.

Often, translating the Python SDK command to R is straightforward. In many cases, replacing = with <- and . with $ is sufficient.

For example, this Python command:

fw = flywheel.Client()

Translates to this in R with reticulate:

fw <- flywheel$Client()

However, updating container metadata and using iterators/generators are two key activities that require help from reticulate functions.

Updating Containers

The Python SDK expects Python dictionaries as input to update containers. In order to construct these containers, we uses reticulate’s dict function.

For example, this Python code provides a dictionary when updating the subject:

subject.update({'type': 'human', 'sex': 'female'})

When translating to R, we create a dictionary like this:

subject$update(dict('type'='human', 'sex'='female'))

Using Iterators/Generators

Python iterators/generators need to be wrapped in reticulate’s iterate function

For example, the python code for iter_find() on acquisitions:

for acquisition in fw.acquisitions.iter_find('files.type=dicom'):
        print(acquisition.label)

Becomes the following in R:

for (acquisition in iterate(fw$acquisitions$iter_find('files.type=dicom'))){
    print(acquisition$label)
}