BIDS Stats Models Object Reference#

This section defines the valid keys and values in a BIDS Stats Model. A BIDS Stats Model is defined in a JSON document.

Object definitions#

BIDSStatsModel

A BIDS Stats Model is a JSON file that defines one or more hierarchical models on brain imaging data.

Node

A node represents an estimator that applies to a given level of analysis.

Transformations

Transformations describe modifications of variables to prepare a design matrix.

Model

The model to fit to the collection of input images.

HRF

Specification of a hemodynamic response function (HRF) model.

Options

Estimation options that are common to multiple estimation packages.

Contrast

Contrasts are weighted sums of parameter estimates (betas) generated by a model fit.

DummyContrasts

Dummy contrasts are contrasts with one condition, a weight of one, and the same name as the condition.

Edge

An Edge connects two Nodes, indicating the outputs (contrasts) of the Source Node are to be made available as inputs to the Destination Node.

BIDSStatsModel is the top-level structure, while the remaining classes define sub-structures. To demonstrate this hierarchy, here we show an example model in this structure, with missing fields rendered as None:

BIDSStatsModel(
    Description="My first BIDS model: a simple 2-condition contrast.",
    Name="my_first_model",
    BIDSModelVersion="1.0.0",
    Input={"task": ["stroop"]},
    Nodes=[
        Node(
            Description=None,
            Level="Run",
            Name="run",
            GroupBy=["run", "subject"],
            Transformations=None,
            Model=Model(
                Description=None,
                Type="glm",
                X=["congruent", "incongruent", "nuissance1", "nuissance2", 1],
                Formula=None,
                HRF=None,
                Options=None,
                Software=None,
            ),
            Contrasts=[
                Contrast(
                    Description=None,
                    Name="incongruent_vs_congruent",
                    ConditionList=["incongruent", "congruent"],
                    Weights=[1, -1],
                    Test="t",
                )
            ],
            DummyContrasts=None,
        ),
        Node(
            Description=None,
            Level="Subject",
            Name="subject",
            GroupBy=["contrast", "subject"],
            Transformations=None,
            Model=Model(
                Description=None,
                Type="meta",
                X=[1],
                Formula=None,
                HRF=None,
                Options=None,
                Software=None,
            ),
            Contrasts=None,
            DummyContrasts=DummyContrasts(Description=None, Contrasts=None, Test="t"),
        ),
        Node(
            Description=None,
            Level="Dataset",
            Name="dataset",
            GroupBy=["contrast"],
            Transformations=None,
            Model=Model(
                Description=None,
                Type="glm",
                X=[1],
                Formula=None,
                HRF=None,
                Options=None,
                Software=None,
            ),
            Contrasts=None,
            DummyContrasts=DummyContrasts(Description=None, Contrasts=None, Test="t"),
        ),
    ],
    Edges=[
        Edge(Description=None, Source="run", Destination="subject", Filter=None),
        Edge(Description=None, Source="subject", Destination="dataset", Filter=None),
    ],
)
{
  "Name": "my_first_model",
  "BIDSModelVersion": "1.0.0",
  "Description": "My first BIDS model: a simple 2-condition contrast.",
  "Input": {
    "task": ["stroop"]
  },
  "Nodes": [
    {
      "Level": "Run",
      "Name": "run",
      "GroupBy": ["run", "subject"],
      "Model": {
        "X": ["congruent", "incongruent", "nuissance1", "nuissance2", 1],
        "Type": "glm"
      },
      "Contrasts": [
        {
          "Name": "incongruent_vs_congruent",
          "ConditionList": ["incongruent", "congruent"],
          "Weights": [1, -1],
          "Test": "t"
        }
      ]
    },
    {
      "Level": "Subject",
      "Name": "subject",
      "GroupBy": ["contrast", "subject"],
      "Model": {
        "X": [1],
        "Type": "meta"
      },
      "DummyContrasts": {"Test": "t"}
    },
    {
      "Level": "Dataset",
      "Name": "dataset",
      "GroupBy": ["contrast"],
      "Model": {
        "X": [1],
        "Type": "glm"
      },
      "DummyContrasts": {"Test": "t"}
    }
  ],
  "Edges": [
    {"Source": "run", "Destination": "subject"},
    {"Source": "subject", "Destination": "dataset"}
  ]
}

Note that each structured field has a Description subfield. Any JSON object may have a Description key where the author or generator of a model can provide an explanation of the section.

How to read object definitions#

The object definitions linked above have a common structure that may not be obvious. Some confusion may arise from a conflation of JSON and Python terms, as the definitions are written as Pydantic models and use Python typing to constrain values.

Here we present an “explainer model” that demonstrates different types, how they appear in the definition, and their corresponding JSON.

object ExplainerModel#

This is an example model.

In schema terms, the structure that defines a JSON object is a “model”. To avoid confusion with BIDS Stats Models and more general notions of mathematical or statistical models, we will use “schema model” to unambiguously refer to this concept.

A schema model defines a JSON object with fields that have both a name and a type, where “type” indicates the range of acceptable values.

Below are a number of fields that demonstrate the types we use in this specification. These types can be mixed and matched somewhat. Probably the most complex-looking field is Contrast.Weights.

field StringField: str [Required]#

This field is called StringField and has type str.

This type indicates that any string is acceptable, while other types (even string-like) are unacceptable.

Valid example:

{"StringField": "any string value"}

Invalid examples:

{"StringField": 0}
{"StringField": ["list", "of", "strings"]}
field IntField: str [Required]#

This strict integer field must have integer values.

Valid example:

{"IntField": 0}

Invalid examples:

{"IntField": 1.0}
{"IntField": "2"}
field SomeOptions: Literal[1, 'stringval'] [Required]#

The Literal type allows a specific value or set of values.

Valid examples:

{"SomeOptions": 1}
{"SomeOptions": "stringval"}

Invalid examples:

{"SomeOptions": "1"}
{"SomeOptions": "differentstringval"}
{"SomeOptions": 2.0}
field ArrayOfInts: List[str] [Required]#

JSON arrays appear as List types, and List[str] means the values must be integers.

Valid example:

{"ArrayOfInts": [1, 2]}
field Object: Dict[str, Any] [Required]#

JSON objects appear as Dict types.

The general form is Dict[str, <value-type>], because the field name in a JSON object is always a string. To allow for any values, including integers, strings or nested types, we use Any.

Valid example:

{"Object": {"key1": "stringval", "key2": 1}}

We use these when objects with arbitrary names can be used. If the full list of valid names is known, we define a new schema model.

field ObjectOfObjects: Dict[str, Dict[str, Any]] [Required]#

Nested objects can start to have hairy type signatures.

Because Dict[str, <value-type>] is the general form for objects, the general form for objects of objects is Dict[str, Dict[str, <value-type>]].

The actual result is fairly straightforward, though:

{
  "ObjectOfObjects": {
    "field1": {"subfield": "value of ObjectOfObjects.field1.subfield"},
    "field2": {"intsubfield": 1}
  }
}
field ModelField: DummyContrasts [Required]#

Schema models are nested objects with pre-determined names and types.

This is a specialized version of Object, and you can follow the link in the type to learn more.

Here, the DummyContrasts model defines the structure of the object.

Valid example:

{"ModelField": {"Contrasts": ["contrast1", "contrast2"], "Test": "t"}}
field UnionField: str [Required]#

Unions mean that a value could take multiple types.

Valid examples:

{"UnionField": 1}
{"UnionField": "stringval"}

Invalid examples:

{"UnionField": 2.0}
field OptionalField: Optional[str]#

OptionalField could be present or absent.

Up to now, all fields have been required. If a field is optional, its type will be wrapped in Optional[] and will not have [Required] in its signature.

field ListOrListOfLists: Union[List[int], List[List[int]]] [Required]#

A 1- or 2D array of integers.

To allow this form, we need to use the Union type with List[] and List[List[]]. At the “bottom” of the type is an integer.

Contrast.Weights has this form, but instead of int, it permits integers, floats or strings because it is intended to allow values like 0.5 or "-1/3".