Skip to content

Using MkDocs macros in the BIDS specification

We use mkdocs-macros to standardize how some aspects of the BIDS specification are rendered in HTML. Macros make it easy to achieve a consistent style throughout the specification, and changing a given macro will automatically change all appropriate paragraphs in the specification.

Below you will find answers to frequently asked questions regarding using macros in the BIDS specification.

What are macros and why use them?

A macro is a rule or pattern that specifies how an input should be mapped to output. Macros are very useful for standardizing the output format for items such as tables. You might already be familiar with using macros from other tools such as Excel.

MkDocs (the tool we use to turn the markdown version of the BIDS specification into HTML pages) supports macros. In the specification document, we use these macros to standardize the format of items such as tables and examples.

The following is an example of a macro used to create consistent "file tree" layouts in the documentation. The macro takes a single parameter, the directory tree to be displayed in JSON format. If you insert the following in the BIDS markdown document:

{{ MACROS___make_filetree_example(

   {
   "sub-01": {
      "func": {
         "sub-control01_task-nback_bold.json": "",
         },
      }
   }

) }}

The result would be rendered in the specification document as:

└─ sub-01/
   └─ func/
      └─ sub-control01_task-nback_bold.json

What kind of input information are required by macros?

Some macros only use the arguments you directly supply in the macro call.

Other macros use information (such as metadata terms) from external sources, and you will need to provide links to this information as part of the call. For example, parts of the BIDS specification are formalized into a "schema" so that requirements in the specification can be automatically checked by validators. Several of the macros incorporate information from this schema to assure consistency.

What macros are available?

All the macros we use are in listed in this Python file.

Name Purpose Uses schema Example
make_columns_table Generate a markdown table of TSV column information. Yes link
make_entity_table Generate an entity table from the schema, based on specific filters. Yes link
make_entity_definitions Generate definitions and other relevant information for entities in the specification. Yes link
make_filename_template Generate a filename template from the schema, based on specific filters. Yes link
make_filetree_example Generate a filetree snippet from example content. No link
make_glossary Yes link
make_metadata_table Generate a markdown table of metadata field information. Yes link
make_suffix_table Generate a markdown table of suffix information. Yes link
define_common_principles List the common principles and definitions. Yes link
define_allowed_top_directories Create a list of allowed top-level directories with their descriptions. Yes link
render_description Renders the description of an object in the schema. Yes [link] (???)

When should I use a macro?

Most typo corrections and minor changes to the BIDS specification do not require you to use macros. Even adding a table may not require you to use macros unless the table falls into one of the categories listed in the macros table.

If you want to add content with a macro and need help, do not hesitate to contact a member of the bids-maintainers for help. To do this, you can either mention an individual maintainer by their GitHub username or mention the whole team (@bids-standard/maintainers).

Do I need learn how to program to use those macros?

Macros don't require programming knowledge to use. You do need to know what arguments the macro expects. The examples linked in the above table provide useful guidance in this respect.

Macros that extract information from the schema also require you to use the correct terms in the schema. This process is illustrated in the next section.

Note that under the hood the macros themselves call python code that can be found in the tools directory. If you are interested in creating a new macro for users, this would be useful.

Anything else I need to know if I need to insert a new macro call?

One nice thing for the people who will come after you (or yourself in 6 months when you get back to the document you just edited) is to leave a comment before the macro to quickly explain what it does and where to find more information about it.

It could for example look like this:

<!--
This block generates a metadata table.
The definitions of these fields can be found in src/schema/...
and a guide for editing at <link>.
-->

{{ MACROS\_\_\_make_metadata_table( { "AcquisitionMode": "REQUIRED",
"MoonPhase": "OPTIONAL", "ImageDecayCorrected": "REQUIRED",
"ImageDecayCorrectionTime": "REQUIRED",

      ...

} ) }}

How-To and Examples

Writing directory content examples

One of the simplest macro we use helps us create consistent "file tree" examples that would look like this in the final document:

└─ sub-01/
   └─ func/
      └─ sub-control01_task-nback_bold.json

To do this get this output, your macro call would look like this:

{{ MACROS___make_filetree_example(

   {
   "sub-01": {
      "func": {
         "sub-control01_task-nback_bold.json": "",
         },
      }
   }

) }}

When you have complex files and directory structure, we suggest you use this Jupyter notebook for sandboxing your example before you insert the macro call into the markdown document.

Generating tables

Say you want to edit the content of table of the Reconstruction section for the PET page.

The HTML version of the this section is here:

https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/09-positron-emission-tomography.html#reconstruction

In the markdown document, it is here:

https://github.com/bids-standard/bids-specification/blob/master/src/04-modality-specific-files/09-positron-emission-tomography.md#reconstruction

GitHub's way of directly rendering markdown documents makes it a bit harder to read, so if you opened the markdown document in your code editor it would look like this.

#### Reconstruction

{{ MACROS___make_metadata_table(
   {
      "AcquisitionMode": "REQUIRED",
      "ImageDecayCorrected": "REQUIRED",
      "ImageDecayCorrectionTime": "REQUIRED",
      "ReconMethodName": "REQUIRED",
      "ReconMethodParameterLabels": "REQUIRED",
      "ReconMethodParameterUnits": "REQUIRED",
      "ReconMethodParameterValues": "REQUIRED",
      "ReconFilterType": "REQUIRED",
      "ReconFilterSize": "REQUIRED",
      "AttenuationCorrection": "REQUIRED",
      "ReconMethodImplementationVersion": "RECOMMENDED",
      "AttenuationCorrectionMethodReference": "RECOMMENDED",
      "ScaleFactor": "RECOMMENDED",
      "ScatterFraction": "RECOMMENDED",
      "DecayCorrectionFactor": "RECOMMENDED",
      "DoseCalibrationFactor": "RECOMMENDED",
      "PromptRate": "RECOMMENDED",
      "RandomRate": "RECOMMENDED",
      "SinglesRate": "RECOMMENDED",
   }
) }}

HINT: if you want to see the "raw" content of a file on Github you can always press the raw button that is on the top right of the document you are browsing on Github.


This section calls the macro make_metadata_table to create the table when building the HTML version of that page.

The macro will create the different columns of the table:

  • Key name
  • Requirement level
  • Data type
  • Description

A general description of that macro call would look like this:

{{ MACROS___make_metadata_table(
   {
      "TermToRender": "REQUIREMENT_LEVEL plus anything else after", "Extra content you want to append after the description of that term."
   }
) }}

To know what to put in the different columns, the macro will go and look into the metadata.yaml file in the BIDS schema and find the entry that correspond to the term you want to add.

And in the above example, all the information about AcquisitionMode would be read from that section.

If you had to write the markdown equivalent of the general example for the macro call above it would give a table that would look like this:

Key name Requirement level Data type Description
TermToRender REQUIREMENT_LEVEL plus anything else after string whatever description was in the metadata.yml

Modifying a term in the table

So if you want to change the content of what will appear in the HTML table, you need to edit this metadata.yml file.

If you wanted to add some extra content to that table, but without modifying the definition in the schema, then you could just add some extra content into the macro call.

#### Reconstruction

{{ MACROS___make_metadata_table(
   {
      "AcquisitionMode": "REQUIRED", "But only when the acquisition was done on a full moon."

       ...

Why would you NOT want to modify the content of the yml file directly ?

Well the same term can be used in different parts of the BIDS specification and some details that might apply to, say, PET might not apply to how the term is used for MRI. So we can use the schema for the common part and add extra content where necessary in the macro call.

So always better to check if that term is not used somewhere else before making a change in the yml file. When in doubt add the change directly in the macro call and ask the BIDS maintainers for help.

Adding a new term to the table

Say you wanted to add a new term MoonPhase to the table, on the second row. You would do it like this. But this would only work if the metadata.yml file contains an entry for MoonPhase. If this is the case because the term already exists and is used somewhere in the BIDS specification, you are in luck and you can just stop there.

#### Reconstruction

{{ MACROS___make_metadata_table(
   {
      "AcquisitionMode": "REQUIRED",
      "MoonPhase": "OPTIONAL",
      "ImageDecayCorrected": "REQUIRED",
      "ImageDecayCorrectionTime": "REQUIRED",

      ...

   }
) }}

If the term does not exist, you need to add it. YML files have a fairly strict syntax where spaces and indentation matter a lot. You can a mini intro to YML files in the Turing way: https://the-turing-way.netlify.app/reproducible-research/renv/renv-yaml.html

In practice, you should try to use a code editor that tells you when your syntax is wrong.

Should I create a macro if I need a new kind of table?

As a rule of thumb, no, unless it is clear that this kind of table will reappear many times in the future in the specification. But this is usually hard to predict so better start with a table in Markdown.

If later we see that the same type of table keeps reoccuring the specification we could create a macro to generate them.

Why use macros at all?

Seriously why did you have to make it so complicated just to have pretty tables? Are macros that necessary ? Couldn't we just have everything in Markdown?

In principle we could, and before we started working on the schema that's exactly what we did. But there are several good reasons to use macros.

When a definition gets repeated in different places, we could just copy-paste it. But when you start having several copies of that definition, if you have to modify it, you then need to edit several files and never forget any of them. So this becomes very error prone.

So it becomes better to have one central place for that definition and grab that definition every time we need to reuse it.

In practice this applies the DRY principle ("Don't Repeat Yourself") to the specification:

"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system".

Having one centralized place where we put all our definitions can be useful when we want other tools (the BIDS validator, bids-matlab...) to use the content of the specification.

This is where the BIDS schema (those .yml files we talked about above) comes in as it is meant to be a machine readable version of the specification.

And so to avoid having to maintain the SAME definition in both the schema and specification, we started using macros to generate the specification from the schema.