Repository: mne-tools/mne-bids

Issue:
Write a participants.json file
The BIDS-validator raises a warning for each column in a TSV file that has a non-custom label. Custom labels are all those labels that are explicitly defined in the specification, such as `duration` and `onset` in the events.tsv file.

We are getting the following warning:

```
	1: [WARN] Tabular file contains custom columns not described in a data dictionary (code: 82 - CUSTOM_COLUMN_WITHOUT_DESCRIPTION)
		./participants.tsv
			Evidence: Columns: age, sex, group not defined, please define in: /participants.json

```

So whenever we write age, sex, or group to our participants TSV file, we should also create an accompanying JSON file that elaborates on these column labels.


Discussion:
I feel this shouldn't be needed for `age` `sex` and `group`. After looking at the specification it's fairly vague about this point as it only says
> As with all other tabular data, those additional phenotypic information files can be accompanied by a JSON file describing the columns in detail.

The 3 extra columns we write are very obvious what they are. I feel if anything the specification should maybe be updated to indicate that these 3 columns are the *recommended* ones and have the validator not require descriptions for.

If we do write a json it would be very basic though I guess...
```json
{
 "age": "The age of the participant",
 "sex": "The gender of the participant",
 "group": "Group within the project the participant belongs to"
}
```
I disagree. The specification is quite explicit and it says:

> The purpose of this file is to describe properties of participants such as age, handedness, sex, etc. In case of single 
session studies this file has one compulsory column participant_id that consists of 
sub-<participant_label>

Let's just add the `json` ...
also, the JSON should be explicit about the levels of the factors we are introducing and the units for continuous variables. See this example:

```json
{
    "age": {
        "Description": "age of the participant",
        "Units": "years"
    },
    "group": {
        "Description": "group the participant belonged to in the experimental paradigm",
        "Levels": {
            "examplea": "blabla",
            "exampleb": "loremipsum"
        }
    },
    "sex": {
        "Description": "sex of the participant",
        "Levels": {
            "m": "male",
            "f": "female"
        }
    }
}
```

Resolving patch:
diff --git a/mne_bids/mne_bids.py b/mne_bids/mne_bids.py
--- a/mne_bids/mne_bids.py
+++ b/mne_bids/mne_bids.py
@@ -218,7 +218,7 @@ def _events_tsv(events, raw, fname, trial_type, overwrite=False,
     return fname
 
 
-def _participants_tsv(raw, subject_id, group, fname, overwrite=False,
+def _participants_tsv(raw, subject_id, fname, overwrite=False,
                       verbose=True):
     """Create a participants.tsv file and save it.
 
@@ -231,8 +231,6 @@ def _participants_tsv(raw, subject_id, group, fname, overwrite=False,
         The data as MNE-Python Raw object.
     subject_id : str
         The subject name in BIDS compatible format ('01', '02', etc.)
-    group : str
-        Name of group participant belongs to.
     fname : str
         Filename to save the participants.tsv to.
     overwrite : bool
@@ -249,8 +247,8 @@ def _participants_tsv(raw, subject_id, group, fname, overwrite=False,
 
     subject_info = raw.info['subject_info']
     if subject_info is not None:
-        genders = {0: 'U', 1: 'M', 2: 'F'}
-        sex = genders[subject_info.get('sex', 0)]
+        sexes = {0: 'n/a', 1: 'M', 2: 'F'}
+        sex = sexes[subject_info.get('sex', 0)]
 
         # determine the age of the participant
         age = subject_info.get('birthday', None)
@@ -265,10 +263,10 @@ def _participants_tsv(raw, subject_id, group, fname, overwrite=False,
         else:
             subject_age = "n/a"
 
-        data.update({'age': [subject_age], 'sex': [sex], 'group': [group]})
+        data.update({'age': [subject_age], 'sex': [sex]})
 
     df = pd.DataFrame(data=data,
-                      columns=['participant_id', 'age', 'sex', 'group'])
+                      columns=['participant_id', 'age', 'sex'])
 
     if os.path.exists(fname):
         orig_df = pd.read_csv(fname, sep='\t')
@@ -297,6 +295,34 @@ def _participants_tsv(raw, subject_id, group, fname, overwrite=False,
     return fname
 
 
+def _participants_json(fname, overwrite=False, verbose=True):
+    """Create participants.json for non-default columns in accompanying TSV.
+
+    Parameters
+    ----------
+    fname : str
+        Filename to save the scans.tsv to.
+    overwrite : bool
+        Defaults to False.
+        Whether to overwrite the existing data in the file.
+        If there is already data for the given `fname` and overwrite is False,
+        an error will be raised.
+    verbose : bool
+        Set verbose output to true or false.
+
+    """
+    cols = OrderedDict()
+    cols['participant_id'] = {'Description': 'Unique participant identifier'}
+    cols['age'] = {'Description': 'Unique participant identifier',
+                   'Units': 'years'}
+    cols['sex'] = {'Description': 'Biological sex of the participant',
+                   'Levels': {'F': 'female', 'M': 'male'}}
+
+    _write_json(cols, fname, overwrite, verbose)
+
+    return fname
+
+
 def _scans_tsv(raw, raw_fname, fname, overwrite=False, verbose=True):
     """Create a scans.tsv file and save it.
 
@@ -403,7 +429,7 @@ def _coordsystem_json(raw, unit, orient, manufacturer, fname,
                 'HeadCoilCoordinateUnits': unit  # XXX validate this
                 }
 
-    _write_json(fid_json, fname, overwrite)
+    _write_json(fid_json, fname, overwrite, verbose)
 
     return fname
 
@@ -633,8 +659,10 @@ def write_raw_bids(raw, bids_basename, output_path, events_data=None,
     scans_fname = make_bids_basename(
         subject=subject_id, session=session_id, suffix='scans.tsv',
         prefix=ses_path)
-    participants_fname = make_bids_basename(prefix=output_path,
-                                            suffix='participants.tsv')
+    participants_tsv_fname = make_bids_basename(prefix=output_path,
+                                                suffix='participants.tsv')
+    participants_json_fname = make_bids_basename(prefix=output_path,
+                                                 suffix='participants.json')
     coordsystem_fname = make_bids_basename(
         subject=subject_id, session=session_id, acquisition=acquisition,
         suffix='coordsystem.json', prefix=data_path)
@@ -658,8 +686,9 @@ def write_raw_bids(raw, bids_basename, output_path, events_data=None,
     manufacturer = MANUFACTURERS.get(ext, 'n/a')
 
     # save all meta data
-    _participants_tsv(raw, subject_id, "n/a", participants_fname, overwrite,
+    _participants_tsv(raw, subject_id, participants_tsv_fname, overwrite,
                       verbose)
+    _participants_json(participants_json_fname, True, verbose)
     _scans_tsv(raw, os.path.join(kind, bids_fname), scans_fname,
                overwrite, verbose)
 


Test patch: