Quick Reference

Introduction

The Flywheel SDK is a python toolbox that provides programmatic access to the Flywheel API endpoints. The quick reference section provides a searchable summary of common SDK calls with basic examples.

Setup

Installation

The latest python package can be installed using pip:

pip install flywheel-sdk

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:

# import flywheel package
import flywheel

# 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()
print('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()

Connecting to your Flywheel Client

There are two primary ways to connect to your flywheel Client:

  • With the CLI: If you have already installed the flywheel CLI, and have logged in (as described here), then you may access your instance’s API with the following command:

import flywheel
from pprint import pprint

# Access the flywheel API
fw = flywheel.Client()

# Check user Info
user_info = fw.get_current_user()

print(f"Firstname: {user_info.firstname} \n"
      f"Lastname: {user_info.lastname} \n"
      f"Email: {user_info.email}")
  • Without the CLI: If you do not have the CLI installed or have not logged in to your instance with it, you can specify the API key you would like flywheel to use. The flywheel API key is usually of the following format:

<flywheel instance URL>:<numbers_and_letters>

for example:

upenn.flywheel.io:d9Ee3fjDeXwaeHp0tff8

(Note that this API key is fake. Don’t try to use it.)

This string should be considered like a password, and should be kept secret.

You can log in to the instance associated with this key using the following code:

import flywheel
from pprint import pprint

# Enter your API key into python and pass that
# value in when calling flywheel.Client()
api_key = "<MY_FLYWHEEL_API_KEY>"
fw = flywheel.Client(api_key)

# Check user Info
user_info = fw.get_current_user()

print(f"Firstname: {user_info.firstname} \n"
      f"Lastname: {user_info.lastname} \n"
      f"Email: {user_info.email}")

Flywheel Objects

The Group Object

The object returned by these finder functions is a python dictionary with a number of keys:

  • _id: the group ID, same as label

  • label: The group label, same as ID

  • permissions_template: A template of default permissions to give new projects

  • roles: Any roles the current API key has in the group

  • permissions: A list of users in the group and their assigned roles/permissions

  • created: When the group was created

  • modified: When the group was last modified

  • tags: Any user assigned tags

You can view the structure of this dictionary using pprint:

from pprint import pprint
pprint(group)

The Project Object

The project object (Say that ten times fast!) is a python dictionary. These are the most common key/value pairs you’ll likely use:

  • label: The project label

  • info: A dictionary representing any metadata information added to the project by users

  • description: A description of the project

  • group: The group to which the project belongs

  • _id: The project ID, given as a unique flywheel letter/number identification string

  • parents: Any parent objects to the container. For projects, this is only the group

  • created: The date which the project was created

  • modified: The date which the project was last modified

  • permissions: A list of users who have access to this project, and their permissions

  • files: A list of any non-acquisition file attached to the project

  • tags: Any tags added to the project

  • analyses: A list of any project-level analyses

Note that the info, files, and analyses fields can be very long, depending on the number of items in each.

The Subject Object

  • _id: The subject ID

  • project: The unique Flywheel ID of the parent project

  • firstname: The subject’s first name

  • lastname: The subject’s last name

  • sex: The subject’s sex

  • cohort: The cohort the subject belongs to

  • type: The type of subject (“Human”, “Animal”, or “Phantom”)

  • race: The subject’s race

  • ethnicity: The subject’s ethnicity

  • species: The subject’s species

  • strain: The subject’s strain

  • label: The subject’s label in flywheel

  • tags: Any tags added to the subject

  • info: Custom metadata added to the subject

  • files: Non-acquisition/image files attached to a subject

  • parents: The subject’s parent containers in flywheel

  • created: The date the subject was created

  • modified: The date the subject was last modified

  • permissions: A list of users who have access to this subject and their permissions

  • notes: Any notes added to the subject

  • analyses: Any analyses attached to the Subject container.

The Session Object

  • label: The name of the session in Flyhweel

  • info: Any additional metadata information added to the session

  • project: The unique Flywheel ID of the project that this session belongs to

  • subject: The subject object (and it’s metadata) associated with the session

  • age: The age of the subject associated with the session

  • weight: The weight of the subject associated with the session

  • _id: The unique Flywheel ID for this session

  • info_exists: Indicates if there is any metadata in the “info” section

  • parents: The container’s parents object

  • created: The date/time that the Session was created in Flywheel

  • modified: The date/time that the Session was last modified in Flywheel

  • permissions: A list of users who have access to this session, and their associated permissions

  • group: The groupID associated with the session

  • files: A list of any files attached to the session that are not part of the child acquisitions

  • notes: Any notes added by users to the session

  • tags: Any tags added by users to the session

  • analyses: A list of any analysis containers associated with the session

  • acquisitions: A finder object with all the sessions’s acquisitions

Finding Objects

The first thing you may want to do is locate data in flywheel. There are a number of ways to find and view data on flywheel. The most basic way is to have the client return all containers of a certain type:

Finding Data

Flywheel has two primary methods to find data using the SDK:

  1. Finders

  2. Queries

Finders

Each container type has it’s own finder in flywheel. It their most basic form, finders will return all possible matches. Finders are called with the .find() method.

To specify what container type you’re looking for, it can be called after a container type from the flywheel client:

  • fw.projects.find()

  • fw.subjects.find()

  • fw.sessions.find()

etc.

In this case, each finder will return EVERY project/subject/session that you have access to on the site.

This is equivalent to simply calling:

  • fw.projects()

  • fw.subjects()

  • fw.sessions()

Until you place filter arguments in find()

You can pass in filters to limit the containers returned. The filters can set conditions based off of the containers’ metadata. For example, if you want to only get subjects that belong to a certain project, you can filter for that property as follows:

fw.subjects.find(‘project=<PROJECT_ID>’)

Alternatively, you can search for all subjects DIRECTLY from that project’s container object:

project = fw.get_project('<PROJECT_ID>')
subjects = project.subjects.find()
# OR
subjects = project.subjects()

Further, finders can be applied here as well:

` subject = project.subjects.find(‘created>2018-10-31’)`

Will return all subjects created after October 31st, 2018, in the given project.

find_first & iter_find

There are two other varieties of finders:

  1. find_first()

  2. iter_find()

These two behave exactly as find() in that they can be called directly from the client:

  • fw.subjects.find_first()

  • fw.sessions.iter_find()

or from a container:

project = fw.get_project('<PROJECT_ID>')
subjects = project.subjects.find_first()
sessions = project.sessions.iter_find()

Additionally, they can take filter arguments just like find():

fw.subjects.find_first(“label=’My Subject’”)

They differ in what they return.

*find_first* stops searching flywheel when it finds the first match, and returns that match. This can be useful to simply see if your search is working, and if ANY data is matching to it.

*iter_find* returns an iterator. This can be useful for searches that return large numbers of containers. find() will retrieve the metadata of every container that matches the search filter, generate a list of that data for python, and return that to your terminal. This can take time when hundreds or thousands of search results need to be returned. iter_find() returns an iterator that sends the metadata for the matching containers one at a time, rather than all at once.

This can be useful when there is some sort of processing loop that you’re doing to your data.

For example, appending a keyword to all subjects created after a certain date:

project = fw.get_project('<PROJECT_ID>')

# INCORRECT - This method will be slow if there is a large number of subjects
subjects = project.subjects.find('created>2019-09-09')
for sub in subjects:
    label = sub.label
    sub.update({'label':f"old_cohort_{label}"})


# CORRECT - This method will be faster than the one above if there is a large number of subjects
subjects = project.subjects.iter_find('created>2019-09-09')
for sub in subjects:
    label = sub.label
    sub.update({'label':f"old_cohort_{label}"})


sessions = project.sessions.iter_find()

Queries

Queries are an extension of our advanced search function in the SDK.

You can generate queries just like you’d generate an advanced search, and passing the query into the fw.search() function:

age = 64

query = f'session.age >= 0 AND session.age <= {age} AND ' \
        f'project.label = Sample AND ' \
        f'file.classification.Measurement = T1 AND ' \
        f'file.info.PatientIdentityRemoved = YES'

results = fw.search({'structured_query': query, 'return_type': 'acquisition'}, size=10000)

you must specify the query and the return type. The return type specifies which container type it will return, regardless of what the query criteria is. This will return any containers who have a parent or child container that the query criteria satisfies.

the size parameter specifies the number of results to return, typically limited to 100, with a maximum of 10000.

As an example, the following query would return all subjects in the project labeled “Test Project”:

query = 'project.label = "Test Project"'
results = fw.search({'structured_query': query, 'return_type': 'subject'}, size=10000)

And likewise, the following query would return all subjects who have a child acquisition named “multi_echo”:

query = 'acquisition.label = multi_echo'
results = fw.search({'structured_query': query, 'return_type': 'subject'}, size=10000)

Groups

View All Groups

To view all the groups your account has access to on this flywheel instance:

all_groups = fw.groups()

print('Flywheel Groups:')
for group in all_groups:
      print(f"\t{group.id}")

Find a Specific Group

If you would like to examine a specific group in more detail, you can directly load that one group with the following:

group_name = 'scien'

group = fw.get(group_name)
# Equivalently, but possibly slower:
group = fw.lookup(group_name)
group = fw.groups.find(f"_id={group_id}")

Projects

View All Projects

To view all the projects your account has access to:

projects = fw.projects()

print('Flywheel Projects:')
for project in projects:
      print(f"\t{project.label}")

View All Projects In a Group

Using a Group ID

To view all projects in a specific group that your account has access to using a group id:

group_id = 'scien'
projects_in_group = fw.projects.find(f"group={group_id}")

print(f"Flywheel Projects in {group_id}:")
for project in projects_in_group:
      print(f"\t{project.label}")

The flywheel finder .find() can be usd to search for values in the Project Object Metadata. Only keywords found in the object can be used in the query. More details on flywheel finders can be found here [LLL].

Using a Group’s Project List

If you have already captured a group object (See Find a Specific Group), you can call projects() to return all projects in that group:

group_id = 'scien'
group = fw.get(group_id)

projects_in_group = group.projects()
print(f"Flywheel Projects in {group_id}:")
for project in projects_in_group:
      print(f"\t{project.label}")

View One Specific Project

Using the Client Projects Finder

To view one specific project in a group using the project label and group ID:

group_id = 'scien'
project_label = 'cmrr_deid'

one_project = fw.projects.find(f"group={group_id},label={project_label}")
# Or Equivalently:
one_project = fw.projects.find_first(f"group={group_id},label={project_label}")
one_project = fw.projects.find_one(f"group={group_id},label={project_label}")
one_project = fw.lookup(f"{group_id}/{project_label}")

The differences the finders/lookup functions are covered in more detail here

Note that if a group has multiple projects with the same name, it will be easier to access a project using it’s unique Flywheel ID. If you know a project’s flywheel ID, you can access it directly using the steps in the next section:

Using a Group’s Project Finder

If you already have the group object loaded that the project belongs to, it may be faster to locate the project using the group.projects.find() method:

group_id = 'scien'
group = fw.get(group_id)

project_label = 'cmrr_deid'
one_project = group.projects.find(f"label={project_label})
# Or Equivalently:
one_project = group.projects.find_first(f"label={project_label})
one_project = group.projects.find_one(f"label={project_label})

Get a Project’s Flywheel ID

Flywheel assigns unique IDs to every object. This ID can be useful for looking up data, as a single group can have multiple projects with the same name, but the ID will always be unique. The project ID is stored in the Project Object Metadata, and can be accessed in the following way:

   group_id = 'scien'
   project_label = 'cmrr_deid'

   one_project = fw.lookup(f"{group_id}/{project_label}")

   project_id = one_project.id

..

This ID can also be found in the hyperlink shown in your browser’s navigation bar when viewing that project on flywheel:

_images/Project_ID_browser.png

While the “finder” and “lookup” methods need to query flywheel for a project label, you can use the flywheel ID to directly retrieve a project, making this the fastest option for accessing data:

project = fw.get(project_id)

You can use the fw.get() method for Projects, Subjects, Sessions, and Acquisitions [LLL]

Subjects

Finding All Subjects in a Project

Using a project’s subject list

To view all subjects in a specific project we use the project ID to get the project object, as it contains information on all subjects within itself. If you have the project object, or know the project ID, you can print all subjects within the project as follows:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

subjects_in_project = project.subjects()
print(f"{len(subjects_in_project)} Subjects found")
for subject in subjects_in_project:
      print(f"\t{subject.label}")

A project may also have a large number of subjects, so it may be desirable to use the subject iterator to loop through them to save on time waiting for the API to respond:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

subjects_in_project = project.subjects.iter()
print(f"{len(subjects_in_project)} Subjects found")
for subject in subjects_in_project:
      print(f"\t{subject.label}")

View All Subjects in a Project That Meet Filter Criteria

You can use the Flywheel finders to search for subjects with specific metadata values. For example, you can search for all male subjects under the age of 30:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

subjects_in_project = project.subjects.find("age<30,sex=male")
print(f"{len(subjects_in_project)} Subjects found")
for subject in subjects_in_project:
      print(f"\t{subject.label}")

If the subject has a custom metadata field in the “info” section, that too can be used with flywheel finders. For example:

subjects_in_project = project.subjects.find("info.custom_info_field=<desired_value>")

View one subject in a Project

Using a Project’s Subject Finder

Or if you have the project object, you can query that, using only the desired subject label:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

subject_label = 'ExampleSubject'
subject_in_project = project.subjects.find(f"label={subject_label}")

Sessions

View All Sessions in a Project

Using a Project’s Session List

If you already have the project object, you can view all sessions in a specific project using the project’s session list:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

sessions_in_project = project.sessions()

print(f"Flywheel Sessions in {project_id}:")
for session in sessions_in_project:
      print(f"\t{session.label}")

to call the iterator from the project:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

sessions_in_project_iterator = project.sessions.iter()

print(f"Flywheel Sessions in {project_id}:")
for session in sessions_in_project_iterator:
      print(f"\t{session.label}")

View all Sessions for one Subject

Using a Subject’s Session List

If you already have the subject object, you can get the sessions as follows:

subject_id = '5ebd50b5bfda5102996aa0e5'
subject = fw.get(subject_id)
subject_sessions = subject.sessions()

View all Sessions in a project with a specific key

It’s sometimes useful to retrieve all sessions of a certain type (‘Baseline’, or ‘Resting’). If this in formation is stored in the session label, it can easily be retrieved as follows:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

sessions = project.sessions.find("label=baseline")
print(f"{len(sessions)} Sessions Found:")
for ses in sessions:
    print(f"\t{ses.label})

Regular expressions can also be used with the =~ symbol, in case for some reason each subject has a slightly different session name, but that always fits a pattern.

For example, assume that every subject has two resting state scans, each performed in a different session:

Session Names:

resting-baseline

resting-followup

func-baseline

func-followup

If we were only interested in all baseline sessions, we could perform the following search:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

sessions = project.sessions.find("label=~.*baseline")

And if we only wanted resting state sessions, we could perform the following:

project_id = '5ebd4ff8bfda5102856aa084'
project = fw.get(project_id)

sessions = project.sessions.find("label=~resting.*")

Read more about using finders here.

Getting One Session

From the Flywheel Instance URL

A session ID can be found in the flywheel URL when in the “session” tab of a project, and in session view mode, as shown below:

_images/Session_ID_browser.png

The session can be loaded directly with fw.get(session_id).

From a Subject’s Session Finder

In this example, because the subject is specified with its unique flywheel string, there is no need to specify the group and project - that information is stored within the subject.

subject_id = '5efcdf14957ea0030bc8a8e7'
ses_label = 'Resting-baseline'
subject = fw.get(subject_id)

session = subject.sessions.find(f"label={ses_label}")

Acquisitions

View All Acquisitions in a Session

Using a Sessions’s Acquisition List

Each Session object has an “Acquisition” finder object with every acquisition for that session. To view all Acquisitions in a Session, use the following:

session_id = '5efcdf14957ea0030bc8a8e7'
session = fw.get(session_id)
acquisitions = session.acquisitions()

print(f"{len(acquisitions)} acquisitions Found:")
for acq in acquisitions:
    print(f"\t{acq.label})

Finding Specific Acquisitions

Using a Sessions’s Acquisition Finder

In Flywheel, the only container with an Acquisition finder is the Session container. A Session’s Acquisition Finder can be used to locate all acquisitions in that Session, or to filter them using the finder. For example, looking for any Acquisitions with the word “resting” in the label can be done as follows:

session_id = '5efcdf14957ea0030bc8a8e7'
session = fw.get(session_id)
acquisitions = session.acquisitions.find('label=~.*resting.*')

print(f"{len(acquisitions)} acquisitions Found:")
for acq in acquisitions:
    print(f"\t{acq.label})

Using the Client’s Acquisition Finder

The only other way to search for acquisitions is using the client’s finder. This can be useful if you want to find all acquisitions of a certain type in a project. For example, if you wanted to locate all acquisitions with the label “BOLD_task_checkerboard” in the project “checkerboard_study”:

project = fw.projects.find_one('label=checkerboard_study')
acquisition_label = "BOLD_task_checkerboard"
acquisitions = fw.acquisitions.find(f"parents.project={project.id},label={acquisition_label}")

print(f"{len(acquisitions)} acquisitions Found:")
for acq in acquisitions:
    print(f"\t{acq.label})

Files

Every container, from Projects to Acquisitions, can have files attached to them. At the Acquisition level, these files are simply the actual scan files, and are displayed in flywheel under the acquisition. For the other containers, these files can be found under their “attachments” sections.

Files are stored in lists, and have no searchable finder objects associated with them.

View All Files in an Acquisition

Because the project and session ID’s are readily available from the U.I., you’ll likely use these ID’s more than any other to find other containers. For example, to view all files in an acquisition, I would go through the session that the acquisition belongs to, since I can quickly copy and paste the session ID from the UI:

session = fw.get('5efce8d7957ea00337c8a8ab')
acquisition = session.acquisitions.find_one('label=rfMRI_REST_AP_Run1')

print(f"acquisition {acquisition.label} has the following files:")
for acq_file in acquisition.files:
    print(f"\t{acq_file})

Find One File in an Acquisition

Finding a file can be done quickly using list comprehension:

file_name = 'rfMRI_REST_AP_12.nii.gz'
file = [f for f in acquisition.files if f.name==file_name]

File names must be unique, so this is guaranteed to only return one file. However, it’s still in a python list due to the structure of the call, so to return the file object itself we make one slight change:

file_name = 'rfMRI_REST_AP_12.nii.gz'
file = [f for f in acquisition.files if f.name==file_name][0]

Find All Files of A Certain Type

Typically, an acquisition will have one DICOM file, and a number of NIFTI files, depending on the type of scan and the amount of preprocessing being done. To find nifti files in an acquisition, the following search can be performed:

filetype = 'nifti'
nifti_files = [f for f in acquisition.files if f.type==filetype]

Learn more about the supported file types here.

Learn more about working with files here.

Container Parents

Every Flywheel container, except for groups, have a parents metadata field. The parents field has the following keys:

-acquisition

ID of the parent acquisition container

-analysis

ID of the parent analysis container

-group

ID of the parent group

-project

ID of the parent project

-session

ID of the parent session

-subject

ID of the parent subject

If there is no parent container for a given field, the value is None. For example, a flywheel project will have a parent group, but no parent acquisiton, analysis, session, or subject. This is determined by the flywheel container hierarchy [LLL]

Creating Objects

Add Group

Create a new group, with an id of my_group and a label of My Test Group:

group_id = fw.add_group(flywheel.Group('my_group', 'My Test Group'))

Add Project

Create a new project that belongs to group_id with a label of My Test Project.

project = group.add_project(label='My Test Project')

Add Subject

Create a new subject with a label of Subject 01

subject = project.add_subject(label='Subject 01')

Add Session

Create a new session with a label of Session 01.

session = subject.add_session(label='Session 01')

Add Acquisition

Create a new acquisition with a label of Localizer, and upload a file.

acquisition = session.add_acquisition(label='Localizer')

acquisition.upload_file('localizer.nii.gz')

Setting Object Metadata

Once an object is created, it’s common to want to set or modify certain metadata parameters. Object metadata is stored in key/value pairs. Flywheel provides some default keys that all (or most) objects will share, such as “creation date”, “label”, and “parents”. There is also a custom field titled “info” that users can upload any amount custom key/value pairs to. These examples will show how to set some common parameters, as well as the custom info.

Metadata Format

Metadata is passed to flywheel in the form of key/value pairs using the update() function, with a few specific exceptions that will be covered. In the Python SDK, these key/value pairs are stored as dictionaries. For example, updating the label of a container in Flywheel would typically be done with the python dict {‘label’:’new_label’}, and called as follows:

container.update({'label':'new_label'})

Certain keys expect values of a certain data type. For example, label expects a string, and timestamp expects a timestamp from the datetime python package. Other metadata keys have special functions for modifying them, such as tags and notes. Finally, some metadata fields cannot be modified after creation, such as created, modified, and id.

Setting Acquisition Timestamps

Acquisition objects have timestamp fields that indicate the date of acquisition of the file. This is separate from the created and modified fields, which only pertain to the creation and modification of the file in Flywheel.

The timestamp object requires a dateteime object, with a timezone included. Working with timezones in any programming language can be…tricky to say the least. We recommend that you review some documentation on the subject, but a simple example will be provided below:

from datetime import datetime
import pytz


# Get a session object in flywheel:
session = fw.get_session('5f185ffd957ea0084cc8a85f')

# Create an acquisition
acq = ses.add_acquisition(label='MyNewAcquisition')

# get a timezone object, in this case UTC:
timezone = pytz.utc

# Create a timestamp for when the acquisition was taken (outside of flywheel)
# datetime can specify year, month, day, hour, minute, second, millisecond, and timezone:
# Let's set the date for January 1st, 4:30am, 2020.  Better times.
acq_time = datetime(2020, 1, 1, 4, 30, tzinfo=timezone)

# Now update the acquisition's timestamp
acq.update({'timestamp': acq_time})
Containers

Projects, Subjects, Sessions, Acquisitions and Analyses are all different types of Containers. Containers in Flywheel all support the following features:

Tags

Tags are concise labels that provide descriptive metadata that can be searched on. Available tags are managed on the Group.

# See tags on a session
session = fw.get(session_id)
print(', '.join(session.tags))

# Add a tag to a session
session.add_tag('Control')

# Remove a tag from a session
session.delete_tag('Analysis Required')

Notes

Notes are user-entered, human readable metadata attached to a container. They are timestamped and attributed to the user that entered them.

from pprint import pprint

# See notes on a session
session = fw.get(session_id)
pprint(session.notes)

# Add a note to a session
session.add_note('This is a note')

# Delete a note from a session
session.delete_note(session.notes[0].id)

Info

Info is free-form JSON metadata associated with a container or file.

from pprint import pprint

# Print the info for an acquisition
acquisition = fw.get(acquisition_id)
pprint(acquisition.info)

# Replace the entire contents of acquisition info
acquisition.replace_info({ 'splines': 34 })

# Add additional fields to acquisition info
acquisition.update_info({ 'curve': 'bezier' })

# Delete fields from acquisition info
acquisition.delete_info('splines')

Files

Files are a set of file attachments associated with a container. See also Dealing with Files.

from pprint import pprint

# List files on an acquisition
acquisition = fw.get(acquisition_id)

for f in acquisition.files:
  print('Name: %s, type: %s' % (f.name, f.type))

# Upload a file to an acquisition
acquisition.upload_file('/path/to/file.txt')

# Download a file to disk
acquisition.download_file('file.txt', '/path/to/file.txt')

# Files can also have metadata
pprint(acquisition.files[0].info)

acquisition.replace_file_info('file.txt', {'wordCount': 327})

File Classification

Flywheel supports an extensible, multi-dimenstional classification scheme for files. Each dimension of classification is referred to as an aspect. The available aspects are determined by the file’s modality.

For example, the MR modality provides the Intent, Measurement and Features aspects. In addition, the Custom aspect is always available, regardless of modality.

from pprint import pprint

# Display the aspects defined in the MR modality
mr = fw.get_modality('MR')
pprint(mr)

# Replace a file's modality and classification
acquisition.replace_file_classification('file.txt', {
        'Intent': ['Structural'],
        'Measurement': ['T2']
}, modality='MR')

# Update a file's Custom classification, without changing
# existing values or modality
acquisition.update_file_classification('file.txt', {
        'Custom': ['value1', 'value2']
})

# Delete 'value1' from Custom classification
acquisition.delete_file_classification('file.txt', {
        'Custom': ['value1']
})

Timestamps [NEW]

Objects with timestamps and created/modified dates provide helper accessors to get those dates in the local (system) timezone, as well as the original timezone in the case of acquisition and session timestamps.

For example:

# Acquisition Timestamp (tz=UTC)
print(acquisition.timestamp.isoformat())

# Acquisition Timestamp (tz=Local Timezone)
print(acquisition.local_timestamp.isoformat())

# Acquisition Timestamp (tz=Original Timezone)
print(session.original_timestamp.isoformat())

Age at Time of Session [NEW]

Sessions have a field for subject age at the time of the session, in seconds. There are also helper accessors to get age in years, months, weeks and days.

For example:

# Subject age in seconds
print('Subject was {} seconds old', session.age)

# Subject age in years
print('Subject was {} years old', session.age_years)

Modifying Metadata On Existing Objects

Metadata on containers can be modified in the following ways:

  1. update()

  2. update_info()

  3. replace_info()

These can be called on all containers and files that have metadata.

Update

Update will modify any metadta fieds specified, and leave all unspecified fields unchanged.

For example:

subject.update(sex='male', info={'key1':'val1'})

This will change (or add) the values for sex and key1 in the custom info of this subject. All other values remain unchanged.

Update_info

update_info will allow you to pass in only a info metadata object. Any keys passed in will be updated if they exist, or added of they do not. All unspecified keys will remain untouched.

Note that this is equivalent to passing the metadata object to update() after the info key.

example:

session.update_info({'hair': 'black', 'group': 'control', 'IQ': 107})

# Equivalent to:
session.update(info={'hair': 'black', 'group': 'control', 'IQ': 107})

Replace_info()

This will completely replace ALL info with the metadata object passed into this argument. This means that all existing key/value pairs in the “info” section are erased, and reaplaced with this object.

For example, regardless of any data already present in acquisiton.info, after calling this command, the only information present will be 'key1':'val1'

acquisition.replace_info({'key1':'val1'})

EXAMPLE: Set permissions for a list of users across multiple projects

Let’s say you have a list of user ID’s, and a permission level that they should have for all projects in a group:

User ID

Permission Level

user1@site.com

admin

user2@site.com

rw

user3@site.com

admin

userX@site.com

ro

NOTE that this code will assign access to EVERY project within a given group. Extreme caution must be taken when assigning user permissions to ensure that ONLY the correct access is given to the correct projects.

Setting Group Permissions

By default, You will have admin permissions on any group that you create. You may wish to modify these permissions, or add other users to the group.

Let’s add an example user to our group with “read write” permissions:

# Access Types:
# 'admin'- admin
# 'rw' - read/write
# 'ro'- read only

access_type = 'rw'
user_id = 'example@user.com
my_group.add_permission({'access':access_type, '_id':user_id})

Group Roles

Roles are different from group permissions - Roles apply to Project access within a given group. Roles and permissions is a detailed topic that we suggest you review. Here we will only demonstrate how to use them. Custom roles can be made, giving you granular control over a user’s access to a project. For example, you cna create a role where a user ONLY has permission to modify metadata, but not files.

Typically, you would have to create and name your own roles before assigning them here. However, flywheel does provide three default roles that you can always use: #. admin #. read/write #. read only

Below is a quick example of how to get these default roles and add them to your group. Remember that these roles define the permissions that we can assing users for PROJECTS within this group, and do not directly indicate what permissions a user has regarding what they can do to the GROUP itself.

admin_id = [role.id for role in fw.get_all_roles() if role.default_flywheel_role == 'admin'].pop()
rw_id = [role.id for role in fw.get_all_roles() if role.default_flywheel_role == 'rw'].pop()
ro_id = [role.id for role in fw.get_all_roles() if role.default_flywheel_role == 'ro'].pop()

fw.add_role_to_group(my_group.id, {'_id': admin_id})
fw.add_role_to_group(my_group.id, {'_id': rw_id})
fw.add_role_to_group(my_group.id, {'_id': ro_id})

These are now roles we can assign to users on individual projects within the group.

Any groups you can see here, you have access to or are part of. If you see a group you’d like to use for your new project, we can grab it using the fw.get() command, and pass in the group ID (printed using the command above):

my_group = fw.get('group-id')

Or you can create a new group (if you have the correct site permissions): Note that “id” must be a lowercase string with no spaces. It’s a good idea to make this relatively short, as it will be used to access your group programmatically, and no one wants to type out ‘my_very_long_descriptive_group_id’. The group “label” on the other hand can be a human friendly name with spaces.

new_group = flywheel.Group(id='my_id', label='My Label')
my_group_id = fw.add_group(new_group)  # Returns the group id as a string
my_group = fw.get(my_group_id)

Create a new Project

Now that we have our group, let’s add a project to it. If we didn’t just create this group, there may already be projects in it. Let’s check and see what projects exist already, so we know what names we can’t use:

import flywheel

project = my_group.projects()
print(f"GROUP{'':10} PROJECT")
print("----------------------------------")
for project in projects:
    print(f"{project.group:15} {project.label}")

Now let’s choose a name for our new project that doesn’t already exist:

new_project_label = "My New Project"
project = group.add_project(label=new_project_label)

Add a Subject to a Project

Now that we have our new project, let’s add a new subject to it. You can simply specify the label as above, but you can also specify any of the metadata fields covered in the “Subject Object” header of the “Quick Reference” Section. [LLL]. We will take advantage of that and specify the sex and name of this subject:

metadata = {
    'label': 'Subject2',
    'firstname': 'Lisa',
    'lastname': 'Simpson',
    'sex': 'female'
    }

subject = project.add_subject(metadata)

Add a Session to a Subject

We can now add a new session to this subject. We could again use a python dictionary to create this session with metadata already included. Sessions can have the metadata fields specified in the “Session Object” header of the “Quick Reference” section [LLL].:

metadata = {
    'label': 'Session1',
    'age': 20 * 365 * 24 * 60 * 60, # Age in seconds at time of scan
    'weight': 140
    }

session = subject.add_session(metadata)

Add an Acquisition to a Session

Let’s create a simple acquisition to house a simple anatomical T1 image:

label = 'anat_T1'
acquisition = session.add_acquisition(label=label)

In the next section we will upload a file to this acquisition. But before we continue, we will set a unique metadata field to acquisitions: the Timestamp.

Acquisition Timestamp

The acquisition timestamp is a time-zone specific timestamp for when the file within the acquisition was created, or when the scan took place. This is different from the Flywheel metadata tags Created and Modified, which refer to when the container was created and modified in Flywheel. To set the acquisition timestamp, you must know the time/date/timezome when the data was acquired:

from datetime import datetime
import pytz

# get a timezone object, in this case UTC:
timezone = pytz.utc

# Create a timestamp for when the acquisition was taken (outside of flywheel)
# datetime can specify year, month, day, hour, minute, second, millisecond, and timezone:
# Let's set the date for January 1st, 4:30am, 2020.  Better times.
acq_time = datetime(2020, 1, 1, 4, 30, tzinfo = timezone)

# Now update the acquisition's timestamp
acquisition.update({'timestamp': acq_time})

Upload a file to an Acquisition

We are now ready to upload our scan to the acquisition:

file_path = "/path/to/file/T1_Image.nii.gz"
flywheel_file = acquisition.upload_file(file_path)

# In turn, we can download this file as follows:
acquisition.download_file('T1_Image.nii.gz','/path/to/downloaded/file/T1_Image.nii.gz')

Setting file Metadata

If we have metadata on this file (Such as dicom header fields, or any kind of BIDS sidecar .json file), we can add it to the info section of the flywheel metadata for that file. this info will be visible in the UI under the “Custom Information” tab. The metadata must be passed in as a python dictionary. The original format of this data can be arbitrary (csv, yaml, json, etc), as long as you can load the

# Load in metadata as a python dictionary as key/value pairs.
# This can be any format, but we'll assume we have it saved as a .json file

import json


with open('file_info.json') as json_file:
    metadata = json.load(json_file)

flywheel_file.update_info(metadata)

Uploading a file to any container with metadata

Files can also be uploaded to a container with just the container ID. File Metadata can be uploaded concurrently as a json string with this call as well:

import json

# Get a container ID:
container = fw.subjects.find_first()
container_id = container.id

# Create a Metadata object:
md = {'info':{'hair color':'brown','motion level':'low'}}

# Upload the file with metadata
file = '/path/to/file.nii.gz'
fw.upload_file_to_container(container_id, file, metadata=json.dumps(md))

Uploading many files to an Acquisition

Multiple files can be uploaded by either creating a script with nested for loops, or by using the fw upload CLI command.

Downloading Data

All containers in Flywheel can be downloaded.

The easiest way to download an entire container is simply to use download_tar or download_zip which take the following arguments:

  • containers

  • dest_file

  • include_types

  • exclude_types

for example:

fw.download_tar([acquisition], '/tmp/acquisition.tar')
fw.download_zip([acquisition], '/tmp/acquisition.zip')

Additionally, you can donwload individual files using the download() function:

files = session.files
file_to_download = files[0]
download_location = f"/tmp/{file_to_download.name}"
file_to_download.download(download_location)