Tutorial

Login to your account

All actions in the platform need to be authenticated. In qmenta.core, the platform.post() function has an instance of qmenta.core.platform.Auth for authentication. In order to create an Auth instance, you must use the login() function.

The following combinations of parameters are supported. api_key, ask_for_2fa_input, and base_url must always be passed as keyword arguments. username, password, and code_2fa may be passed positionally or as keyword arguments.

Username and password

from qmenta.core.auth import Auth

auth = Auth.login(username='tutorial@qmenta.com', password='MyPassword1%')

Username, password, and 2FA code

If your account has two-factor authentication enabled, provide the code sent to your phone:

auth = Auth.login(
    username='tutorial@qmenta.com',
    password='MyPassword1%',
    code_2fa='123456'
)

Alternatively, set ask_for_2fa_input=True to be prompted for the code interactively when it is required:

auth = Auth.login(
    username='tutorial@qmenta.com',
    password='MyPassword1%',
    ask_for_2fa_input=True
)

API key

Your API key can be generated in the QMENTA platform under My profile → Credentials:

auth = Auth.login(api_key='MyApiKey')

Targeting a specific environment

All of the above accept an optional base_url parameter to connect to a platform environment other than the default (https://platform.qmenta.com):

auth = Auth.login(
    username='tutorial@qmenta.com',
    password='MyPassword1%',
    base_url='https://staging.qmenta.com'
)

When username, password, and api_key are all provided, username + password takes precedence and the api_key is ignored.

The returned auth object is used in all the post() calls in the remainder of this tutorial.

Instead of hard-coding the password, it is recommended to use getpass.getpass() or to read it from a configuration file.

Projects

All requests to the platform can be done using a post() call. We use parse_response() to check the validity of the response and to extract the JSON information from the Response object:

from qmenta.core.platform import post, parse_response

projects = parse_response(post(
    auth, 'projectset_manager/get_projectset_list'
))

The returned value will be a list of dictionaries, where each dictionary represents a project and its information. For example:

print(projects)

>>>
[   {   '_id': 1,
        'user_id': 'tutorial@qmenta.com',
        'tags': ['test', 'example'],
        'name': 'Example project',
        'description': 'Example project for the qmenta.core tutorial',
        'time_created': '2020-01-01 08:00:14'
    },
    {   '_id': 2,
        'user_id': 'tutorial@qmenta.com'
        'tags': ['example'],
        'name': 'Another example project',
        'description': 'Of course you can have more than one project',
        'time_created': '2020-01-02 9:00:00'
    }
]

To find a project with a specific name you can use, for example:

[p for p in projects if p['name'].startswith('Example')

>>>

[   {   '_id': 1,
        'user_id': 'tutorial@qmenta.com',
        'tags': ['test', 'example'],
        'name': 'Example project',
        'description': 'Example project for the qmenta.core tutorial',
        'time_created': '2020-01-01 08:00:14'
    }
]

Subjects

Get existing subjects

To get the list of existing subjects in a project, you first activate the project, and then request the list of subjects:

post(auth, 'projectset_manager/activate_project', data={'project_id': 6})
subjects = parse_response(post(auth, 'patient_manager/get_patient_list'))

You can then view the subject data as a dict:

print(subjects[0])

>>>
{   '_id': 5395.0,
    'age_at_scan': None,
    'config':   {   'time_created': {'$date': 1545840544017},
                    'time_modified': {'$date': 1545840557867}
                },
    'container_id': 26817,
    'data_location': 'us',
    'date_at_scan': None,
    'owner': 'Tutorial user',
    'patient_secret_name': '123_4567',
    'qa_comments': '',
    'qa_status': '',
    'site': '',
    'ssid': '1',
    'tags': ['healthy'],
    'user_id': 'tutorial@qmenta.com'
}

Create a new session

To add a new subject to an existing project, you only need to specify its name:

resp = post(
    auth, 'patient_manager/upsert_patient', data={'secret_name': 'john-doe'}
)

parse_response(resp)

>>>
{   'success': 1,
    'message': 'Session data inserted successfully !',
    'ssid': '1'
}

This will automatically create the first session, with session ID ssid = 1. Executing the same command again will add another session with ssid = 2 to the subject. To get the Session IDs belonging to the newly added subject with the given patient_secret_name, execute:

subjects = parse_response(post(auth, 'patient_manager/get_patient_list'))

[(s['_id'], s['patient_secret_name'], s['ssid']) for s in subjects
    if s['patient_secret_name'] == 'john-doe']

>>>
[   (416522.0, 'john-doe', '1'),
    (416523.0, 'john-doe', '2')
]

Upload data

Sessions can be uploaded using the qmenta.core.upload module. To upload a single ZIP file to a project with Project ID 100, use:

from qmenta.core.upload import SingleUpload, FileInfo

upload = SingleUpload(
    auth, '/home/tutorial/data/file0.zip', FileInfo(project_id=100)
)
upload.start()

It is optional to add name, subject_name and session_id as attributes of FileInfo. If they are not specified, values will be chosen automatically. Instead of specifying a ZIP file to be uploaded, a directory may be given, and it will automatically be zipped before uploading.

When uploading a large amount of files, use MultipleThreadedUploads:

from qmenta.core.upload import MultiThreadedUploads, FileInfo

multi_upload = MultipleThreadedUploads(auth)
pid = 100

multi_upload.add_upload(
   '/home/tutorial/data/file1.zip',
   FileInfo(project_id=pid, name='file1')
)

multi_upload.add_upload(
    '/home/tim/data/file2.zip',
    FileInfo(project_id=pid, name='file2')
)

When using MultipleThreadedUploads, the uploads are started automatically, and multiple files will be uploaded in parallel.

In order to see the upload progress, connect to the blinker signals upload-status and upload-progress before starting the uploads. For example:

from blinker import signal

def progress_changed(caller, upload_index):
    single_upload = caller
    bytes_in_mb = 1024.0 * 1024.0
    percentage = (100.0 * single_upload.bytes_uploaded /
                single_upload.file_size)
    progress_message = ('[{:.0f}%] Progress for upload {} ({}): {:.2f}'
                        ' / {:.2f} MB').format(
        percentage,
        upload_index, single_upload.upload_filename,
        single_upload.bytes_uploaded / bytes_in_mb,
        single_upload.file_size / bytes_in_mb
    )
    print(progress_message)


def status_changed(caller, upload_index):
    single_upload = caller
    print('Status changed to {}'.format(single_upload.status.name))


on_progress = signal('upload-progress')
on_progress.connect(progress_changed)
on_status = signal('upload-status')
on_status.connect(status_changed)