Would you like to hear about webinars we're
doing, new features we're adding and projects we're undertaking? Sign up
here to our pleasantly infrequent newsletter!
Query execution time increases proportionally with the amount of data being retrieved. When you query a project with hundreds of subjects or more, it is highly recommended to retrieve a filtered subset.
This section explains how to exploit the Moveshelf API's server-side filtering functionality to retrieve a subset of subjects in a project on Moveshelf that meet a set of subject metadata, session date, and session count criteria. Filtering is particularly useful when you only need to retrieve subjects with certain characteristics (such as a specific diagnosis), or subjects that have a certain number of sessions within a specific time period.
Prerequisites
Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
The function getFilteredProjectSubjects returns a list subjects filtered by subject metadata and by session date and session count. Subject metadata and session filters can be combined or used individually. This section explains how to construct subject metadata filters and session filters, and provides the implementation of an example use case.
Subject metadata filters can be constructed in the following way:
subject_metadata_filters={"key":"<metadata_key>"# e.g., "subject-sex"
"operator":"EQ"# currently, only EQ is supported
"value":"<metadata_value>"# e.g., "Male"
}
It is also possible to concatenate subject metadata filters with a certain logic. For example, to find all female patients diagnosed with Cerebral Palsy or ACL Rupture:
subject_metadata_filters={"logic":"AND",# Supported logics: AND, OR
"filters":[{"key":"subject-sex","operator":"EQ","value":"Female"},{"logic":"OR","filters":[{"key":"subject-diagnosis","operator":"EQ","value":"Cerebral Palsy"},{"key":"subject-diagnosis","operator":"EQ","value":"ACL Rupture"}]}]}
Session filters can be constructed in the following way. Please note that for subjects that meet the requirements defined in session_filters, ALL sessions will still be returned, as the filter operation returns a set of subjects.
session_filters={"sessionDates":{"startDate":Noneor"<startDate>"# start date (inclusive) in "YYYY-MM-DD" format or None,
"endDate":Noneor"<endDate>"# end date (inclusive) in "YYYY-MM-DD" format or None,
},"numSessions":{"min":Noneor<int># minimum number of sessions (inclusive)
"max":Noneor<int># maximum number of sessions (inclusive)
}}
To retrieve all female patients diagnosed with Cerebral Palsy or ACL Rupture with exactly two sessions between 01-01-2013 and 30-04-2020, you could use:
## README: this example shows how we can retrieve sessions from Moveshelf
# using the Moveshelf API.
## General configuration. Set values before running the script
my_project="<organizationName/projectName>"# e.g. support/demoProject
subject_metadata_filters={"logic":"AND",# Supported logics: AND, OR
"filters":[{"key":"subject-sex","operator":"EQ","value":"Female"},{"logic":"OR","filters":[{"key":"subject-diagnosis","operator":"EQ","value":"Cerebral Palsy"},{"key":"subject-diagnosis","operator":"EQ","value":"ACL Rupture"}]}]}session_filters={"sessionDates":{"startDate":"01-01-2013","endDate":"30-04-2020"},"numSessions":{"min":2,"max":2}}## Get available projects
projects=api.getUserProjects()## Select the project
project_names=[project["name"]forprojectinprojectsiflen(projects)>0]idx_my_project=project_names.index(my_project)my_project_id=projects[idx_my_project]["id"]## Get all subjects in the project that fullfill metadata criteria
## Set include_additional_data to True to also retrieve
## clips/trials and data files
subjects=api.getFilteredProjectSubjects(my_project_id,subject_metadata_filters=subject_metadata_filters,session_filters=session_filters,include_additional_data=False)
With the retrieved subset of subjects, you can proceed with further analysis. For example, you may wish to use Python to filter further by intervention metadata or session metadata, or kinematic data. Our examples below may come in helpful during the analysis, but other analysis or statistical programs such as Excel, R or SPSS may also be helpful.
To also retrieve data files (clips and additional data) for the filtered subjects, set include_additional_data=True. This is the most efficient approach for "filter once and download all" workflows:
importrequestsfromconcurrent.futuresimportThreadPoolExecutor# Use a requests.Session for connection pooling and helper functions
MAX_WORKERS=5# Number of threads for parallel processing.
POOL_MAXSIZE=MAX_WORKERS+2# Set slightly higher than max_workers to avoid connection issues
requests_session=requests.Session()adapter=requests.adapters.HTTPAdapter(pool_maxsize=POOL_MAXSIZE)requests_session.mount('https://',adapter)defdownload_with_session(url:str)->dict|None:returndownload_json_file(url,session=requests_session)defdownload_json_file(url:str,session:requests.Session|None=None)->dict|None:try:response=session.get(url)ifsessionelserequests.get(url)decoded_content=response.content.decode()returnjson.loads(decoded_content)exceptExceptionase:print(f"Failed to download or parse {url}: {e}")returnNone## Retrieve subjects with their data files
subjects=api.getFilteredProjectSubjects(my_project_id,subject_metadata_filters=subject_metadata_filters,session_filters=session_filters,include_additional_data=True# Include clips and data files
)## Collect download URLs and file paths
file_extension_to_download='.json'URLs=[]forsubjectinsubjects:forsessioninsubject.get("sessions",[]):forclipinsession.get("clips",[]):foradinclip.get("additionalData",[]):ifad["originalDataDownloadUri"].endswith(file_extension_to_download):URLs.append(ad["originalDataDownloadUri"])# Download additional data in parallel
withThreadPoolExecutor(max_workers=MAX_WORKERS)asexecutor:additional_data=list(executor.map(download_with_session,URLs))
When defining MAX_WORKERS and POOL_MAXSIZE, make sure you add the following line after configuring and initializing the Moveshelf API: api.http.connection_pool_kw['maxsize'] = POOL_MAXSIZE .
Query execution time increases proportionally with the amount of data being retrieved. When you query a project with hundreds of sessions or more, it is highly recommended to retrieve a filtered subset.
This section explains how to exploit the Moveshelf API's server-side filtering functionality to retrieve a subset of sessions in a project on Moveshelf that meet specific subject metadata, session metadata, and date range criteria. Filtering is particularly useful when you only need to retrieve sessions with certain characteristics (such as a specific session type or medical diagnosis).
Prerequisites
Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
The function getFilteredProjectSessions returns a list of sessions filtered by date range, session metadata, and subject metadata. For convenience, you can directly copy the URL of your filtered Sessions overview, which automatically applies all your filters.
The getFilteredProjectSessions function also accepts an optional parameter limit, an integer that specifies the maximum number of sessions to be returned. If not specified, it defaults to 500, mimicking the behavior of the Sessions overview feature. Make sure to specify a larger number if more sessions should be retrieved.
Method 1: Using sessions overview URL
This is the easiest approach. After filtering sessions in Sessions overview, simply copy the URL from your browser:
## README: this example shows how to retrieve filtered sessions from Moveshelf
# using the Moveshelf API.
## Copy the URL from your filtered sessions overview page
url="https://moveshelf.com/project/ID123/sessions?startDate=Last_year&sessioninfo-cancellation=Cancelled"## Extract project_id from URL (comes after /project/)
project_id=url.split('/project/')[1].split('/')[0]## Retrieve filtered sessions
sessions=api.getFilteredProjectSessions(project_id=project_id,session_overview_url=url)print(f"Found {len(sessions)} sessions matching the filters")
Method 2: Using explicit filters (alternative)
For programmatic control, you can specify filters explicitly.
Session metadata filters
session_metadata_filters={{"key":"<metadata_key>",# e.g., "sessioninfo-date-processing-completed"
"operator":"BETWEEN","values":"<metadata_values>"# e.g., start and end dates (inclusive) as ['YYYY-MM-DD', 'YYYY-MM-DD']
},# add more filter dicts as needed
}
Subject metadata filters
patient_metadata_filters=[{"key":"<metadata_key>",# e.g., "subject-sex",
"operator":"EQ","value":"<metadata_value>"# e.g., "Female"
},# add more filter dicts as needed
]
Supported operators include:
IN: Match any of the provided values
EQ: Match a single exact value
BETWEEN: Match a range (for dates)
Note: When building filter objects, use the key values (an array) with the IN and BETWEEN operators, and use the key value (a single value) with the EQ operator.
To also retrieve data files (clips and additional data) for the filtered sessions, set include_additional_data=True. This is the most efficient approach for "filter once and download all" workflows. You can then download and save files in parallel for maximum efficiency:
importrequestsfromconcurrent.futuresimportThreadPoolExecutor# Use a requests.Session for connection pooling and helper functions
MAX_WORKERS=5# Number of threads for parallel processing.
POOL_MAXSIZE=MAX_WORKERS+2# Set slightly higher than max_workers to avoid connection issues
requests_session=requests.Session()adapter=requests.adapters.HTTPAdapter(pool_maxsize=POOL_MAXSIZE)requests_session.mount('https://',adapter)defdownload_with_session(url:str)->dict|None:returndownload_json_file(url,session=requests_session)defdownload_json_file(url:str,session:requests.Session|None=None)->dict|None:try:response=session.get(url)ifsessionelserequests.get(url)decoded_content=response.content.decode()returnjson.loads(decoded_content)exceptExceptionase:print(f"Failed to download or parse {url}: {e}")returnNone## Retrieve sessions with their data files
sessions=api.getFilteredProjectSessions(my_project_id,start_date='2025-01-01',end_date='2025-12-31',session_metadata_filters=session_metadata_filters,patient_metadata_filters=patient_metadata_filters,include_additional_data=True# Include clips and data files
)## Collect download URLs and file paths
file_extension_to_download='.json'URLs=[]forsessioninsessions:forclipinsession.get("clips",[]):foradinclip.get("additionalData",[]):ifad["originalDataDownloadUri"].endswith(file_extension_to_download):URLs.append(ad["originalDataDownloadUri"])# Download additional data in parallel
withThreadPoolExecutor(max_workers=MAX_WORKERS)asexecutor:additional_data=list(executor.map(download_with_session,URLs))
When defining MAX_WORKERS and POOL_MAXSIZE, make sure you add the following line after configuring and initializing the Moveshelf API: api.http.connection_pool_kw['maxsize'] = POOL_MAXSIZE .
To retrieve an existing subject, add the following lines of code to your processing script:
## README: this example shows how we can retrieve a subject from Moveshelf
# using the Moveshelf API.
# For a given project (my_project), retrieve a subject based on either the given MRN (my_subject_mrn)
# or the subject name (my_subject_name)
## General configuration. Set values before running the script
my_project="<organizationName/projectName>"# e.g. support/demoProject
my_subject_mrn="<subjectMRN>"# subject MRN, e.g. "1234567", set to None if you want to use the name
my_subject_name="<subjectName>"# subject name, set to None if you want to use the MRN
## Get available projects
projects=api.getUserProjects()## Select the project
project_names=[project["name"]forprojectinprojectsiflen(projects)>0]idx_my_project=project_names.index(my_project)my_project_id=projects[idx_my_project]["id"]## Find the subject based on MRN or name
subject_found=Falseifmy_subject_mrnisnotNone:subject=api.getProjectSubjectByEhrId(my_subject_mrn,my_project_id)ifsubjectisnotNone:subject_found=Trueifnotsubject_foundandmy_subject_nameisnotNone:subjects=api.getProjectSubjects(my_project_id)forsubjectinsubjects:ifmy_subject_name==subject['name']:subject_found=Truebreakifmy_subject_mrnisNoneandmy_subject_nameisNone:print("We need either subject mrn or name to be defined to be able to search for the subject.")## Print message
ifsubject_found:subject_details=api.getSubjectDetails(subject["id"])subject_metadata=json.loads(subject_details.get("metadata","{}"))print(f"Found subject with name: {subject_details['name']},\n"f"id: {subject_details['id']}, \n"f"and MRN: {subject_metadata.get('ehr-id',None)}")else:print(f"Couldn't find subject with MRN: {my_subject_mrn},\n"f"in project: {my_project}")
To retrieve subject metadata from an existing subject, add the following lines of code to your processing script:
## README: this example shows how we can retrieve subject metadata from an existing subject on
# Moveshelf using the Moveshelf Python API.
# This code assumes you have implemented the 'Retrieve subject' example, and that you have found
# the subject with a given EHR-id/MRN (my_subject_mrn) or name (my_subject_name) within a given
# project (my_project), that you have access to the subject ID and obtained the "subject_details"
## General configuration. Set values before running the script
my_project="<organizationName/projectName>"# e.g. support/demoProject
my_subject_mrn="<subjectMRN>"# subject MRN, e.g. "1234567" or None
my_subject_name="<subjectName>"# subject name, e.g. Subject1 or None
## Add here the code to retrieve the project and get the "subject_details" for that subject
subject_metadata=json.loads(subject_details.get("metadata","{}"))print(f"Found subject with name: {subject_details['name']},\n"f"id: {subject_details['id']}, \n"f"and metadata: {subject_metadata}")
To retrieve a session with a specific date from a subject, add the following lines of code to your processing script:
## README: this example shows how we can retrieve sessions from Moveshelf
# using the Moveshelf API.
# This code assumes you have implemented the 'Retrieve subject' example, and
# that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
# or name (my_subject_name) within a given project (my_project), and that you
# have access to the subject ID.
# Then, for that subject, retrieve the session with the specified date
# (my_session_date).
## General configuration. Set values before running the script
my_project="<organizationName/projectName>"# e.g. support/demoProject
my_subject_mrn="<subjectMRN>"# subject MRN, e.g. "1234567" or None
my_subject_name="<subjectName>"# subject name, e.g. Subject1 or None
my_session_date="<sessionDate>"# "YYYY-MM-DD" format
## Add here the code to retrieve the project and find an existing subject and its "subject_details"
# ... subject_found = True
## Get sessions
sessions=subject_details.get("sessions",[])# Loop over sessions
session_found=Falseforsessioninsessions:try:date_obj=datetime.fromisoformat(session["date"])session_date=date_obj.strftime('%Y-%m-%d')except:session_date=""ifsession_date==my_session_date:session_found=Truesession_id=session["id"]print(f"Found session with date: {my_session_date},\n"f"and id: {session_id}")breakifnotsession_found:print(f"Couldn't find session with date: {my_session_date},\n"f"for subject with MRN: {my_subject_mrn}")
This section explains how to retrieve session metadata for a specified session for a subject with a specified MRN on Moveshelf using the Moveshelf API.
Prerequisites
Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
To retrieve session metadata from a specific session and subject, add the following lines of code to your processing script:
## README: this example shows how we can retrieve session metadata from Moveshelf
# using the Moveshelf API.
# This code assumes you have implemented the 'Retrieve sessopm' example, and
# that you have found session with the specified date
# (my_session_date) for the subject with a given EHR-id/MRN
# (my_subject_mrn) or name (my_subject_name) within a given project (my_project), and
# that you have access to the session ID.
## General configuration. Set values before running the script
my_project="<organizationName/projectName>"# e.g. support/demoProject
my_subject_mrn="<subjectMRN>"# subject MRN, e.g. "1234567" or None
my_subject_name="<subjectName>"# subject name, e.g. Subject1 or None
my_session_date="<sessionDate>"# "YYYY-MM-DD" format
## Add here the code to retrieve the project and find an existing subject and session using "getSessionById"
session_id=session["id"]session_metadata=session.get("metadata",None)print(f"Found session with projectPath: {session['projectPath']},\n"f"id: {session_id},\n"f"and metadata: {session_metadata}")
To export subject and session metadata to an Excel spreadsheet, add the following lines of code to your processing script, right after having Retrieved a subject or list of subjects, OR Retrieved a session or list of sessions. The example uses the MetadataExcelExporter class which can be found in the utils folder as part of metadata_excel_exporter.py in our public GitHub repository. Copy the utils folder inside your work environment and add the following lines of code to your processing script:
fromutilsimportMetadataExcelExporter## ============================================================================
## CONFIGURATION VARIABLES - Edit these before running
## ============================================================================
OUTPUT_FILENAME="Metadata_Export.xlsx"# Specify which fields to export (leave empty for all)
SUBJECT_METADATA_FIELDS:list[str]=['ehr-id','subject-diagnosis']SESSION_METADATA_FIELDS:list[str]=['vicon-height','vicon-weight','interview-assistive-device']# Column header format configuration
# When True: Use metadata field IDs as column headers
# (e.g., 'sessioninfo-comments', 'vicon-leg-length-right')
# When False: Use descriptive labels with tab context
# (e.g., 'Session info: Comments', 'Physical exam 1: Leg length (mm) - right')
USE_METADATA_ID_AS_COLUMN_HEADER:bool=True# Create exporter instance
exporter=MetadataExcelExporter(api,use_metadata_id_as_column_header=USE_METADATA_ID_AS_COLUMN_HEADER)# Export metadata. Uncomment one of the following options (Option 1 or Option 2)
# # Option 1: Export metadata for a list of subjects
# subjects_list = subjects # e.g., the output from Retrieving a subset of subjects
## Note: It is also possible to export metadata from a single subject by assigning
## subjects_list = [subject_details] # where subject_details results from the Retrieve a subject example
# exporter.export_metadata_to_excel(
# project_id=my_project_id,
# output_filename=OUTPUT_FILENAME,
# subjects_list=subjects_list,
# subject_fields=SUBJECT_METADATA_FIELDS,
# session_fields=SESSION_METADATA_FIELDS
# )
# # Option 2: Export metadata for a list of sessions
# # Sessions will be automatically grouped by patient in export_metadata_to_excel
# sessions_list = sessions # e.g., the output from Retrieving a subset of sessions
## Note: It is also possible to export metadata from a single session by assigning
## sessions_list = [session] # where session results from the Retrieve a session example
# exporter.export_metadata_to_excel(
# project_id=my_project_id,
# output_filename=OUTPUT_FILENAME,
# sessions_list=sessions_list,
# subject_fields=SUBJECT_METADATA_FIELDS,
# session_fields=SESSION_METADATA_FIELDS
# )
To display descriptive labels as column headers instead of unique metadata field IDs, set USE_METADATA_ID_AS_COLUMN_HEADER to False.
This section explains how to retrieve trials within a specific condition of a specific session for a subject with a specified MRN on Moveshelf using the Moveshelf API.
Prerequisites
Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
To retrieve the trials within a specific condition, add the following lines of code to your processing script:
## README: this example shows how we can retrieve trials from Moveshelf
# using the Moveshelf API.
# This code assumes you have implemented the 'Retrieve subject' example,
# that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
# or name (my_subject_name) within a given project (my_project), that you
# have access to the subject ID, and you have implemented the
# 'Retrieve session' example to retrieve the specified session (my_session_name).
## General configuration. Set values before running the script
my_project="<organizationName/projectName>"# e.g. support/demoProject
my_subject_mrn="<subjectMRN>"# subject MRN, e.g. "1234567" or None
my_subject_name="<subjectName>"# subject name, e.g. Subject1 or None
my_session_name="<sessionDate>"# "YYYY-MM-DD" format
my_condition="<conditionName>"# e.g. "Barefoot"
## Add here the code to retrieve the project and find an existing subject and its "subject_details"
# ... subject_found = True
## Add here the code to retrieve an existing session and get its details using "getSessionById"
# Get conditions in the session
conditions=[]conditions=util.getConditionsFromSession(session,conditions)condition_exists=any(c["path"].replace("/","")==my_conditionforcinconditions)condition=next(cforcinconditionsifc["path"].replace("/","")==my_condition) \
ifcondition_existselse{"path":my_condition,"clips":[]}trial_count=len(condition["clips"])ifcondition_existselse0clips_in_condition=[clipforclipincondition["clips"]]ifcondition_existsandtrial_count>0else[]# get the id and title of each clip
forclipinclips_in_condition:clip_id=clip["id"]clip_title=clip["title"]print(f"Found a clip with title: {clip_title},\n"f"and id: {clip_id}")
This section explains how to retrieve data (files) within a specific trial of a condition within a session of a subject (specified by MRN) on Moveshelf using the Moveshelf API.
Prerequisites
Before implementing this example, ensure that your processing script includes all necessary setup steps. In particular, you should have:
To retrieve the additional data files with that trial, add the following lines of code to your processing script:
## README: this example shows how we can retrieve additional data from
# Moveshelf using the Moveshelf API.
# This code assumes you have implemented the 'Retrieve subject' example,
# that you have found the subject with a given EHR-id/MRN (my_subject_mrn)
# or name (my_subject_name) within a given project (my_project), that you
# have access to the subject ID, you have implemented the 'Retrieve session'
# example, and you have implemented the 'Retrieve trial' example to get or
# create the specified trial (my_trial) within a specific condition (my_condition).
## Import necessary modules
importrequests# Used to send HTTP requests to retrieve additional data files from Moveshelf
## General configuration. Set values before running the script
my_project="<organizationName/projectName>"# e.g. support/demoProject
my_subject_mrn="<subjectMRN>"# subject MRN, e.g. "1234567" or None
my_subject_name="<subjectName>"# subject name, e.g. Subject1 or None
my_session_name="<sessionDate>"# "YYYY-MM-DD" format
my_condition="<conditionName>"# e.g. "Barefoot"
my_trial="<trialName>"filter_by_extension=None## Add here the code to retrieve the project and find an existing subject and its "subject_details"
# ... subject_found = True
## Add here the code to retrieve an existing session and get its details using "getSessionById"
## Add here the code to retrieve an existing trial and get the ID: clip_id
# get all additional data
existing_additional_data=api.getAdditionalData(clip_id)# if filter_by_extension is provided, only get the data with that extension
existing_additional_data=[datafordatainexisting_additional_dataifnotfilter_by_extensionoros.path.splitext(data["originalFileName"])[1].lower()==filter_by_extension]existing_file_names=[data["originalFileName"]fordatainexisting_additional_dataiflen(existing_additional_data)>0]print("Existing data for clip: ")print(*existing_file_names,sep="\n")# Loop through the data found, and download if the "uploadStatus" is set to "Complete"
# The data will be available in "file_data", from which it can e.g. be used to
# process, or written to a file on local storage
fordatainexisting_additional_data:ifdata["uploadStatus"]=="Complete":file_data=requests.get(data["originalDataDownloadUri"]).content
PDF/image files
PDF files and image files (e.g. ".jpg" or ".png") can be stored as part of a trial and retrieved following the example before, but on Moveshelf we have a dedicated place for PDF/image files, so these are picked up e.g. when exporting to a Word document.
For this, PDF/image files are stored in a condition called "Additional files" with a trial per PDF/image and a name that is typically the same as the PDF/image file name in that trial.
To retrieve, the code as shown above can be used, with the following modifications:
## Modifications needed to extract PDF/image files stored within "Additional files"
my_condition="Additional files"filter_by_extension=".pdf"# to be modified based on the required extension
Raw motion data - ZIP
For raw motion data provided in a ZIP archive, we suggest to use the same "Additional files" condition that is also used for PDF/image files. Specifically, for ZIP files with raw motion data we suggest to use a trial named "Raw motion data". The code to retrieve the data from this trial is similar to the PDF/image example above, but this time specify the specific trial name my_trial you first search for the specific trial:
## Modifications needed to extract ZIP files stored within "Additional files"
my_condition="Additional files"my_trial="Raw motion data"# To be modified based on the exact trial that is required.
filter_by_extension=".zip"# set to None to get all data in the trial