Gear Context
The GearContext class provides an interface for interacting with gear runtime objects and performing common tasks in the lifecycle of a gear:
- Provides access to gear runtime objects:
config.json(runtime configuration with inputs and config values)manifest.json(gear metadata and available options).metadata.json(hierarchy metadata to be updated at end of gear run)
- Instantiates an SDK client when an API key input is provided
- Configures gear logging via
init_logging() - Manages output and work directories
- Opens output files for writing
Structure
The GearContext class is composed of three main components:
-
Config: Parsesconfig.jsonto provide access to runtime configuration values and input files. See Accessing Gear Runtime. -
Manifest: Parsesmanifest.jsonto provide access to gear metadata and available configuration options. See Accessing Gear Metadata. -
Metadata: Manages.metadata.jsonfor updating Flywheel hierarchy metadata without requiring SDK authentication. See Writing Outputs.
Basic Usage
Use GearContext as a context manager to ensure proper initialization and cleanup of gear resources. A typical gear entrypoint (run.py) follows this pattern:
from fw_gear.context import GearContext
from my_awesome_module import do_something
def main(context):
do_something(
dicom=context.config.get_input_path('dicom'),
output_dir=context.output_dir
)
if __name__ == "__main__":
with GearContext() as context:
context.init_logging()
main(context)
Accessing Gear Runtime
Access runtime configuration via the config attribute, which provides access to the Config object that parses config.json.
with GearContext() as context:
# Get input object for named input
input_obj = context.config.get_input('my-input')
# Get path to input file
input_path = context.config.get_input_path('my-input')
# Get configuration option value
cfg = context.config.opts['my-config']
Configuration Options
Access configuration options from the context.config.opts dictionary:
my_speed = context.config.opts.get("speed")
Inputs
Get the full path to a named input file or open it directly:
# Get input file path
dicom_path = context.config.get_input_path("dicom")
# Get input filename
dicom_filename = context.config.get_input_filename("dicom")
# Open input file for reading
with context.config.open_input("dicom", "rb") as dicom_file:
dicom_data = dicom_file.read()
Destination Container
Access the destination container and its parent:
destination = context.config.get_destination_container()
destination_parent = context.config.get_destination_parent()
Warning
Requires at least read-only SDK access. See API key input.
SDK Client
Access the Flywheel SDK Client when an API key input is provided:
project = context.client.lookup("my_group/Project 1")
Accessing Gear Metadata
When manifest.json is available at /flywheel/v0, access it via the manifest attribute:
gear_name = context.manifest.name
gear_version = context.manifest.version
Note
manifest.json is provided at build-time via the Dockerfile, whereas config.json is provided at run-time by the system. The GearContext looks for the manifest at /flywheel/v0 by convention.
Writing Outputs
Output Files
The output directory is available as output_dir (defaults to /flywheel/v0/output). Use open_output() to write files:
# Access output directory path
print(f"Output path: {context.output_dir}")
# Open output file for writing
with context.open_output('out-file.dcm', 'wb') as f:
f.write(dicom_data)
Note
The output directory is cleaned when the context manager exits with an exception unless clean_on_error=False is set when initializing GearContext.
Metadata
Overview
Gears can write metadata to Flywheel containers without SDK authentication using .metadata.json. If this file exists in the output directory, its contents are uploaded as metadata upon job completion.
This allows updating the destination container and its parent containers without SDK access. For metadata updates outside this hierarchy, use SDK-enabled methods.
Info
See the .metadata.json spec for details.
Non-SDK Methods
Update Container Metadata
update_container() adds custom metadata to containers within the hierarchy:
Arguments:
container_type(str): Container typedeep(bool): If True, recursively update subdictionaries**kwargs(dict): Metadata fields to update
Example:
with fw_gear.GearContext() as context:
# Update destination container
info = {"my-metric": "my-value"}
context.metadata.update_container(context.config.destination.type, info=info)
# Update parent session
info = {"my-metric": "my-other-value"}
context.metadata.update_container("session", label="Session 1", info=info)
Update File Metadata
update_file_metadata() adds metadata to input, output, or sibling files:
Arguments:
file_(Any): File name (str), SDK file object, or file dict fromconfig.jsondeep(bool): If True, recursively update subdictionariescontainer_type(str, optional): Parent container type**kwargs(dict): Metadata fields to update
Example:
with fw_gear.GearContext() as context:
context.metadata.update_file_metadata(
file_="out-file.dcm", # Must exist in output directory
container_type="acquisition",
modality="MR",
classification={"Measurement": ["T1"]}
)
Add QC Result to File
add_qc_result() adds QC results to input, output, or sibling files:
Arguments:
file_(Any): File name (str), SDK file object, or file dict fromconfig.jsonname(str): QC result namestate(str): QC state (pass,fail, orna)**data(dict): Additional QC data
Example:
with fw_gear.GearContext() as context:
file_obj = context.config.get_input("input-file")
context.metadata.add_qc_result(
file_obj,
"qc",
"pass",
parameter1="value1",
parameter2="value2"
)
The resulting metadata structure for an acquisition destination:
{
"acquisition": {
"files": [
{
"name": "out_file.dcm",
"info": {
"qc": {
"<gear-name>": {
"job_info": {
"version": "<gear-version>",
"job_id": "62bc8cbcd98b86a919d60ead",
"inputs": {},
"config": {}
},
"my_qc": {
"state": "PASS",
"parameter1": "value1",
"parameter2": "value2"
}
}
}
}
}
]
}
}
Note
.metadata.json is cleaned, validated, and written on context exit * Long lists in logs are truncated By default, invalid metadata fails the gear. Disable with fail_on_validation=False * .metadata.json only updates destination container hierarchy. Use SDK methods for other containers
Add QC Result to Analysis
add_qc_result_to_analysis() adds QC results to the analysis container:
Arguments:
name(str): QC result namestate(str): QC state (pass,fail, orna)**data(dict): Additional QC data
Example:
with fw_gear.GearContext() as context:
context.metadata.add_qc_result_to_analysis(
"analysis-qc",
"pass",
parameter1="value1",
parameter2="value2"
)
Add File Tags
add_file_tags() adds tags to input, output, or sibling files:
Arguments:
file_(Any): File name (str), SDK file object, or file dict fromconfig.jsontags(str or Iterable[str]): Tag or list of tags
Example:
with fw_gear.GearContext() as context:
file_obj = context.config.get_input("input-file")
context.metadata.add_file_tags(file_obj, "tag-01")
SDK-Enabled Methods
Modify Container Info
modify_container_info() wraps the flywheel.modify_container_info() SDK method. Updates container metadata using the set operation:
Arguments:
cont_id(str): Container ID**data(dict): Metadata fields to update
Example:
with fw_gear.GearContext() as context:
new_info = {
"infoA": "value1",
"infoB": "value2"
}
context.metadata.modify_container_info("<container-id>", new_info)
Add QC Result via SDK
add_qc_result_via_sdk() adds QC results to files outside the destination container hierarchy:
Arguments:
cont_(Any): Flywheel container objectname(str): QC result namestate(str): QC state (pass,fail, orna)**data(dict): Additional QC data
Examples:
Add QC result to input file that triggered the gear:
with fw_gear.GearContext() as context:
context.metadata.add_qc_result_via_sdk(
cont_='input_file.dcm',
name="input_qc",
state="PASS",
data={'my-result': 'test'}
)
Add QC result to specific container:
acquisition_cont = client.get_acquisition("<acquisition-id>")
with fw_gear.GearContext() as context:
context.metadata.add_qc_result_via_sdk(
cont_=acquisition_cont,
name="input_qc",
state="PASS",
data={'my-result': 'test'}
)
Complete Example
Update multiple metadata elements:
with fw_gear.GearContext() as context:
# Update destination container
info = {"my-metric": "my-value"}
context.metadata.update_container(context.config.destination.type, info=info)
# Update output file metadata
context.metadata.update_file_metadata(
"out-file.dcm",
modality="MR",
classification={"Measurement": ["T1"]}
)
# Update parent session
info = {"my-metric": "my-other-value"}
context.metadata.update_container("session", label="Session 1", info=info)
This produces the following .metadata.json (for an analysis gear):
{
"analysis": {
"info": {"my-metric": "my-value"},
"files": [
{
"name": "out-file.dcm",
"modality": "MR",
"classification": {"Measurement": ["T1"]}
}
]
},
"session": {
"info": {"my-metric": "my-other-value"},
"label": "Session 1"
}
}
Logging
Configure logging via init_logging(). Sets log level to INFO by default, or DEBUG when a debug configuration option is True:
with GearContext() as context:
context.init_logging()
SDK Profiling
The GearContext automatically logs SDK API usage when debug is enabled in config.json:
import logging
log = logging.getLogger(__name__)
from fw_gear.context import GearContext
with GearContext() as context:
context.init_logging()
proj = context.client.lookup('<group>/<project.label>')
log.info(f"Found project: {proj.id}")
Output:
[ 20210701 12:52:06 INFO fw_gear.logging: 219 - configure_logging() ] Log level is DEBUG
[ 20210701 12:52:07 INFO __main__: 4 - <module>() ] Found project: 603413543321ab021ef0a0a7
[ 20210701 12:52:07 DEBUG fw_gear.context: 618 - __exit__() ] SDK usage:
{'GET': {'https://ga.ce.flywheel.io:443/api/version': 1},
'POST': {'https://ga.ce.flywheel.io:443/api/lookup': 1}}