Skip to content

Gear Building Tutorial Part 5: The Manifest

Introduction

The Manifest

The manifest file provides Flywheel with all the information it needs to run the gear.  This includes metadata such as the gear's author, the gear version, any runtime environment variables, and so on. It also includes any inputs and configuration settings, which the user will have a chance to enter and modify when the gear is run in the Flywheel UI.  

The manifest is written in JSON file format and follows the standard {"key": "value"} syntax.  To learn more about the json format, you can read about their schema.  The manifest must always be named manifest.json.  When the user enters all the input/config settings the manifest requires, Flywheel creates a condensed version of this file called config.json, which has all the information necessary to run the gear.

Instruction Steps

Step 1. Gear Manifest Template

Below, we will start walking through the template manifest.json that is included with the Gear Building Tutorial repository. While dealing with the full file may seem daunting at first, we will step through each section within the context of our example "Hello World" gear. In most cases, the "keys" are set by Flywheel, so most of the work in creating a manifest file for our gear will involve filling in our desired "values".

Flywheel Gear Specification

The example we walk through in this tutorial part is a simplified version of what is currently allowed by the Flywheel gear specification. It does not cover all possible input and config options, nor all of the possible fields allowed under "custom". Users looking to release gears to the Flywheel Gear Exchange are recommended to consult the full gear specifications to understand how to use the manifest.json file to correctly classify and categorize their gear.

Template manifest.json file
{
  "name": "<computer-friendly-name>",
  "label": "<human-friendly-name>",
  "description": "<brief-description-of-the-gear>",
  "version": "<major.minor.patch>",
  "author": "<author-name>",
  "maintainer": "<maintainer-name-and-contact>",
  "cite": "<doi-or-other-citation>",
  "license": "<license>",
  "url": "<link-to-gear-repo>",
  "source": "<link-to-documentation-about-algorithm>",
  "environment": {
    "FLYWHEEL": "/flywheel/v0",
    "MYENV": "<custom-environmental-variable>"
  },
  "custom": {
    "gear-builder": {
      "category": "<analysis|converter|utility|classifier|qa>",
      "image": "<docker/image:version>"
    },
    "flywheel": {
      "suite": "<gear-category-in-flywheel>"
    }
  },
  "inputs": {
    "<input-label>": {
      "description": "<human-friendly-custom-description-of-input>",
      "base": "file",
      "type": {
        "enum": [
          "<file-type>"
        ]
      }
    }
  },
  "config": {
    "<config-label>": {
      "description": "<human-friendly-custom-description-of-config>",
      "type": "string"
    },
    "<config-label>": {
      "description": "<human-friendly-custom-description-of-config>",
      "default": "<default-value>",
      "minimum": "<minimum-value>",
      "maximum": "<maximum-value>",
      "type": "integer"
    }
  },
  "command": "<bash-shell-command-to-run-script>"
}

Let's get started with the first section describing our gear metadata.

Step 2. Gear Metadata

The gear metadata section stores information that Flywheel uses to label the gear and track any changes.  We can begin by simply modifying the values assigned to each key. Below is a description of each key and how it's used in Flywheel.

  • name: a machine-friendly gear name ("hello-world").  This name must only contain lower-case letters, numbers, and dashes (-).
  • label: a human friendly label ("Hello World: My First Gear").  This will show up in the gear selection menu in the Flywheel UI.
  • description: a couple sentences to describe the gear.  This also shows up in the Flywheel UI.
  • version: This tracks the development of the gear, and if there have been and updates/patches.
  • author: Who wrote the gear.
  • maintainer: Who provides support for the gear (optional, but including a contact email is helpful).
  • cite: A DOI or other citation for the algorithm or manuscript associated with the gear.
  • license:  Usually "MIT" or "Apache".  The license with which users may use the gear.
  • url: A link to the code repository (usually GitHub or GitLab) for the gear.
  • source:  A direct link to the source code or documentation for the algorithm used in the gear.

The gear metadata section of our manifest file should now look like:

Gear metadata section
    "name": "hello-world",
    "label": "Hello World: My First Gear",
    "description": "My very first gear, developed along with the flywheel gear building tutorial.",
    "version": "0.1.0",
    "author": "Flywheel User",
    "maintainer": "Flywheel <support@flywheel.io>",
    "cite": "",
    "license": "Apache-2.0",
    "url": "",
    "source": "",

For our simple gear, we are specifically leaving "cite", "url", and "source" blank. Generally, it is preferred to include a "key" in the manifest file and intentionally leave it blank rather than omit unused keys. Doing so helps users understand whether, for example, there simply is no citation for our gear or if there might be but we accidentally omitted that {cite: "value"} when drafting our manifest.json file.

Licenses

Flywheel requires that gears use an OSI Approved license from the SPDX list. If you are unsure what license to pick for your own code, GitHub has created an abbreviated list of open source licenses with simple descriptions of what these licenses do and do not allow. It is important to set some sort of license for your gear, as setting no license means that nobody (not even you) has permission to use or edit the gear. If you are packaging an existing open source software package into a gear, then specify the same license type as the software package.

Step 3. Environment variables

The next section ("environment") is not metadata; it is a list of inputs that will go into the gear's runtime environment.  Any {key: value} pairs in the "environment" section will be passed in with the "key" being the variable name, and the "value" being the variable value. 

The {key:value} pair, {"FLYWHEEL": "/flywheel/v0"} included in the template manifest.json file is equivalent to executing the following command in a bash shell or Terminal window:

export FLYWHEEL="/flywheel/v0"

The "environment" section can be used to set up environment variables directly from the manifest. For example, we can fill in the value of the custom environmental variable "MYENV" as "My Test Environment". Our completed "environment" section should look like:

"environment": {
      "FLYWHEEL": "/flywheel/v0",
      "MYENV": "My Test Environment"
    },

Step 4. Custom Data

The custom data section is where we can put any additional information we want about our gear that is not covered by the Flywheel-specified "keys". The custom section does have some required "keys" we need to specify "values" for. The "gear-builder" and "flywheel" sections have a few keys that we cannot leave empty:

  • Category:  Tell Flywheel if this gear is a utility gear, or an analysis gear. This also determines where the gear appears in the Flywheel UI. The options are "utility", "analysis", "converter", "classifier", or "qa".

    • Utility gears are simple gears whose output is stored in the acquisition container. These are often conversion, QA, or classifier gears, and you can categorize them as so. The UI will treat all these similarly--as utility gears.

    • Analysis gears run a new analysis for the project, subject, or session. Analysis results will be stored in this new type of container--analysis--which is attached to the parent container under which it was run. Analyses have a separate view in the UI.

Since our example gear includes an input file and produces an output file that is not related to either QA or file conversion, we should specify "analysis" under "category".

  • Image: Tell the gear which docker image to use.  This should point to an image on Dockerhub, which will be copied into the Flywheel instance upon gear upload.  Note: We will come back to this "image" after we cover Dockerfiles in Part 6.

  • Suite: Tell Flywheel under which category to list our gear in the UI. Specifying a value for "suite" can make it easier to keep gears organized on our Flywheel UI, so that similar gears are grouped together under broad categories (e.g., "QA", "Image Segmentation", "BIDS", etc.). While not strictly necessary, it is good practice to include a value for the "suite" key to make it easier for users (and ourselves) to navigate the list of installed gears. Since our test gear is not meant for any real data analysis or manipulation, we can define a new custom suite label of "Flywheel Training Gears".

Our completed "custom" section should look like:

"custom": {
    "gear-builder": {
      "category": "analysis",
      "image": "homer/hello-world:0.1.0"
    },
    "flywheel": {
      "suite": "Flywheel Training Gears"
    }
  },

Step 5. Gear Inputs

The gear inputs section tells Flywheel what inputs are needed to run the gear, so that Flywheel can set up the UI to ask the user to provide these inputs. 

Our run.py script expects a single text file as input, so let's look at how we can include this file in our template manifest.json file.

We first need to supply a key label for our input file. We could choose something generic like , "input_file", but that may cause confusion down the line if we were to ever update our run.py script to use two input files. Instead, let's pick a label that is a bit more descriptive, like "message_file".

Next, we need to add values for three properties of our input file:

  • description: A short description of the file input that the user can see in the UI.
  • base: Describing what kind of input it is.  Presently, this can only be "file".
  • type: Type will contain a list of possible file types that this input can be, "text" in our case. 

File types for input files

Including a "type" allows the Flywheel UI to look for this type of file and suggest it when the user goes to select an input. It also enforces that the selected file is of the specified file type.  See our full list of flywheel-supported file types. If the file type is not in the supported list, you can just omit the "type" key.  Flywheel will still allow the subject to choose a file for the input; it will just be unable to suggest default files or enforce file type.

Filling in the "gear_inputs" section, we should end up with this:

    "inputs": {
      "message_file": {
        "description": "the custom message we'll print after our Hello statements",
        "base": "file",
        "type": {
          "enum": [
            "text"
          ]
        }
      }
    },

Step 6. Gear Config

The gear config represents any other input settings that are not files, so anything that might be considered an option when running our script from the command line.  For this gear, we need two more pieces of input, Our name and the number of times to say hello, so we need to create two keys inside the config.  

First, let's define a descriptive key label for our first gear config for specifying Our name. We'll go with "my_name". Next we can give it the following properties:

  • description: Just like the inputs, config settings get a short description that the user will be able to view in the UI.  This should give someone who's unfamiliar with the gear an idea of how to properly set these values.
  • type: Like the input type, this determines what kind of values will be accepted for this config setting.  Unlike the input file, the types for config settings can either be string, integer, number, or boolean (outlined in the Flywheel gear spec).  Since the value of "my_name" should be a string, we set the type to "string".

Now let's consider the second gear config, the number of times to say hello. For this gear config, we can use the key label, "num_rep", short for "number of repetitions".  We can then define the following properties:

  • description: same as above
  • default: (optional) a default value to have in case the user doesn't want to set it manually or neglects to set a value.  We'll set this to "5" so that it repeats the "Hello" statement five times.
  • minimum: (optional) a minimum value.  We'll set this to "0" since you can't say "hello" a negative number of times
  • maximum: (optional) a maximum value.  We'll set this to "10" so that you can't overload the system with 9 million "hello's".
  • type: As above, we will restrict the kind of input allowed.  Since you can only say hello an integer number of times, we'll set this to "integer".

Filling in the "config" section, we should end up with:

    "config": {
      "my_name": {
        "description": "Your name, for the program to say 'hello' to",
        "type": "string"
      },
      "num_rep": {
        "description": "The number of times to say 'hello'",
        "default": "5",
        "minimum": "0",
        "maximum": "10",
        "type": "integer"
      }
    },

Step 7. Gear Command

This is the terminal command that the gear will run when we execute it.  In this case, we just want it to run our run.py file as a python script, so we can simply set it as:

"command": "python3 run.py"

Step 8. The Full Manifest

The full manifest file can be found here and below.

Full manifest.json file
{
  "name": "hello-world",
  "label": "Hello World: My First Gear",
  "description": "My very first gear, developed along with the flywheel gear building tutorial.",
  "version": "0.1.0",
  "author": "Flywheel User",
  "maintainer": "Flywheel <support@flywheel.io>",
  "cite": "",
  "license": "Apache-2.0",
  "url": "",
  "source": "",
  "environment": {
    "FLYWHEEL": "/flywheel/v0",
    "MYENV": "My Test Environment"
  },
  "custom": {
    "gear-builder": {
        "category": "analysis",
        "image": "homer/hello-world:0.1.0"
     },
    "flywheel": {
       "suite": "Flywheel Training Gears"
    }
  },
  "inputs": {
    "message_file": {
      "description": "the custom message we'll print after our Hello statements",
      "base": "file",
      "type": {
        "enum": [
          "text"
        ]
      }
    }
  },
  "config": {
    "my_name": {
      "description": "Your name, for the program to say 'hello' to",
      "type": "string"
    },
    "num_rep": {
      "description": "The number of times to say 'hello'",
      "default": 5,
      "minimum": 0,
      "maximum": 10,
      "type": "integer"
    }
  },
  "command": "python3 run.py"
}

Step 9. The Manifest and the Flywheel UI

Gears in the Flywheel UI

We have not yet covered the steps necessary to build our test gear and upload to a Flywheel UI. These will be covered in a later part. The purpose of this step is to show how the information we include in the manifest file is used by Flywheel to generate the UI for running out gear.

As described above, information the manifest file is used to generate a UI in which users can pass in values to the gear, especially the "custom", "inputs", and "config" sections.  For our test gear, a user would find the gear under Analysis Gear and then under the custom "suite" value, "Flywheel Training Gears", we specified in our manifest file. Our test gear is listed using the human-friendly name we specified under the "label" key in our manifest, "Hello World: My First Gear".

hello-world-gear-selection.png

Before being able to run the gear, the user would be prompted to provide one input file and two config options when launching the gear in a Flywheel UI.

hello-world-gear-ui.png

The first tab (left image) is the Inputs tab, and we can see an input labeled message_file.  This label comes directly from the manifest. Hovering over the information icon next to the input field (red dashed arrow) displays the description we entered in our manifest.  As we can see, input names and descriptions placed in the manifest are important and should be well-thought-out.

The next tab (Configuration, right image) has the two config settings we need.  Like the input, the config names come from the manifest, and the description we set for them can be viewed by hovering over the information icon.

Once the user fills out these values and starts the gear, these three input values will be saved by Flywheel in a config.json file.  Flywheel automatically creates this file and fills it with the user-specified input values and other relevant information needed to run the gear. We will go over how to manually create a config.json file from our manifest.json file in Part 7, when we walk through how to run our test gear on our local machine.

Step 10. The Flywheel SDK

The config.json file has a wealth of gear information that most run scripts will need access to, such as input filepaths and config options.  While it is certainly possible to parse this file ourselves, the Flywheel SDK can automatically read this information in a user-friendly way.  This step will show us how to use the SDK to pull in these values. We'll only cover some very basic functionality here, but it will be enough to get use going for most basic gear development!

Flywheel SDK

The Flywheel SDK is a powerful and extensive package that allows gear developers to interact with the Flywheel API. More information can be found on its documentation page. The Flywheel SDK is available for both Python and Matlab. 

First, let's modify the first few lines of our code by importing the Flywheel SDK package, which we installed in Part 1 of this gear building tutorial. Navigate to the run.py script we created in the previous step and add import flywheel to import the Flywheel SDK package. We also need to import the os and shutil packages to make sure we can copy the output file into the gear output directory.

#!/usr/bin/env python
# The Shebang tells the computer what interpreter
# to call the file with when it runs.
# For more info:https://bash.cyberciti.biz/guide/Shebang

# Import packages needed for the script
# Note: list is alphabetized for readability
import flywheel
import os
import shutil

Step 11. Accessing the config file with Flywheel SDK

As mentioned above, the config.json contains a lot of information needed by the run.py script in order to run the gear. More information can be found here. For the purposes of this gear building tutorial series, we are going to focus on how to use the context.confg object defined in the above code block to access the input files, config options, and output directory path our run.py script needs.

First we need to use the SDK to pull in what is called the gear context. The gear context includes information about the system and environment, the Flywheel instance it's running on, and the inputs and outputs of the gear itself. The information in the config.json file is contained within this gear context object.

To work with the gear context and the config values, let's add the following python calls after the package import section in our run.py file:

# Get the gear context
context = flywheel.GearContext()
# From the gear context, get the config settings  
config = context.config   

Generally, the fields we need from the context.config object for our run.py script can be accessed using similar methods as for Python dictionaries. So, if we wanted to know the value of any arbitrary config option defined in our manifest file, we can use the following:

config_value = context.config['<config_key>']

A slightly more advanced option for grabbing a config value is shown below. This method will return None if the key does not exist.

config_value = context.config.get('<config_key')

In our gear, the values for <config_key> will take on the key names that we specified in the manifest.  For the example used here, our config keys would be "my_name" and "num_reps", and would be callable as follows:

my_name = context.config['my_name']  
num_rep = context.config['num_rep']

Input files behave a bit differently from config options and are accessed using the context.get_input() call. For example, if we wanted to get the path to an input file, we could use the following:

input_path = context.get_input('<input_key>')['location']['path']

However, Flywheel has anticipated the frequent use of certain fields in the gear config, and so custom functions to retrieve those values have been created.  Input file path is one of these fields, and so a simplified call can be made to retrieve these values:

input_path = context.get_input_path('<input_key>')

Similar to accessing input files, the Flywheel SDK has a call for accessing the path to the output directory, making it easy to route any outputs our run script produces to the correct location.

output_path = context.output_dir

SDK context object

A current list of internal functional callable from the context object can be found here.

Step 12. Modifying the Run Script to Use Config Settings

Since our variable values are now all coming from the config file, we can replace the lines where we set our variable values and output directory manually with the following:

## Load in values from the gear configuration  
my_name = config['my_name']  
num_rep = config['num_rep']  

## Load in paths to input files for the gear  
message_file = context.get_input_path('message_file')

...

# Use python's shutil library to copy the file to the output directory
shutil.copy('hello.txt', os.path.join(context.output_dir, 'hello.txt'))

Using gear context in run script

One small drawback about coding with the gear context is that testing and debugging must now be done with the new Flywheel CLI using the fw-beta gear run command. We will cover this in a later section.  For now, we'll just use the SDK in the most basic way to get our inputs.

Step 13. Final Run Script

That's all the extra modification we need to get our run script to run with Flywheel as a gear!  Your final run.py script should look like this:

run.py
#!/usr/bin/env python
# The Shebang tells the computer what interpreter
# to call the file with when it runs.

# Import packages needed for the script
# Note: list is alphabetized for readability
import flywheel
import os
import shutil

# Get the gear context
context = flywheel.GearContext()
# From the gear context, get the config settings  
config = context.config           

## Load in values from the gear configuration
my_name = config['my_name']
num_rep = config['num_rep']

## Load in paths to input files for the gear
message_file = context.get_input_path('message_file')

# While the num_rep variable is greater than zero:
while (num_rep > 0):
    # Open the file hello.txt with the intent to append                                
    with open('hello.txt', 'a') as f: 
        # Write "Hello Name!" to the file every loop             
        f.write("Hello, {}!\n".format(my_name))     

    # Print "Hello Name!" to terminal every loop
    print("Hello, {}!".format(my_name)) 
    # Decrease the num_rep variable by one            
    num_rep -= 1                                    

# Now read the custom message:
# Open the file with the intent to read
message_file = open(message_file,'r')
# Print a blank line to separate the message from the "hello's"               
print('\n')                                         
# Read and print the file contents
print(message_file.read())

# Use python's shutil library to copy the file to the output directory
shutil.copy('hello.txt', os.path.join(context.output_dir, 'hello.txt'))

Wrapping up with the manifest file

In this section, we have gone through the Flywheel gear manifest file, including the minimum information we need to include to run our gear in a Flywheel UI. We briefly looked at how information in the manifest file is used by Flywheel to populate a gear's UI. We also covered how to access information in the manifest file to grab input files and config options for our run script. In the next part we will cover how to create a Dockerfile for our gear so that it can be run by Flywheel.

Previous: Part 4 The Run Script

Next: Part 6 The Dockerfile