Repository: bids-standard/pybids
Pull request #853 (2022-05-05 23:07:18)

FIX: Automodel


Patch:
diff --git a/bids/modeling/auto_model.py b/bids/modeling/auto_model.py
index cd4e80cd..cb94154e 100644
--- a/bids/modeling/auto_model.py
+++ b/bids/modeling/auto_model.py
@@ -3,9 +3,15 @@ from collections import OrderedDict
 import numpy as np
 
 
-def _make_passthrough_contrast(level, contrast_names, test="t"):
-    block = OrderedDict(Level=level, Name=level,
-                        Model={'X': contrast_names})
+def _make_passthrough_contrast(level, contrast_names, model_type='glm', test="t"):
+    gb_dict = dict(Subject=['subject', 'contrast'],
+                   Session=['session', 'contrast'],
+                   Dataset=['contrast'])
+    block = dict(Level=level,
+                 Name=level,
+                 GroupBy=gb_dict[level],
+                 Model={'Type': model_type,
+                        'X': contrast_names})
     contrasts = []
     for cn in contrast_names:
         cdict = OrderedDict(Name=level.lower() + "_" + cn, ConditionList=[cn],
@@ -49,13 +55,24 @@ def auto_model(layout, scan_length=None, one_vs_rest=False):
         model["Name"] = "_".join([base_name, task_name])
         model["Description"] = ("Autogenerated model for the %s task from %s" %
                                 (task_name, base_name))
-        model["Input"] = {"Task": task_name}
+        model["BIDSModelVersion"]= "1.0.0"
+        model["Input"] = {"task": [task_name]}
         nodes = []
 
         # Make run level block
-        transformations = OrderedDict(Name='Factor', Input=['trial_type'])
-        run = OrderedDict(Level='Run', Name='Run',
-                          Transformations=[transformations])
+        transformations = dict(
+            Transformer='pybids-transforms-v1',
+            Instructions=[
+                dict(
+                    Name='Factor',
+                    Input='trial_type'
+                )
+            ]
+        )
+        run = dict(Level='Run',
+                   Name='Run',
+                   GroupBy=['run', 'subject'],
+                   Transformations=transformations)
 
         # Get trial types
         run_nodes = load_variables(layout, task=task_name, levels=['run'],
@@ -67,11 +84,19 @@ def auto_model(layout, scan_length=None, one_vs_rest=False):
         trial_types = np.unique(evs)
         trial_type_factors = ["trial_type." + tt for tt in trial_types]
 
+        run_model = dict(Type='glm', X=trial_type_factors)
         # Add HRF
-        run['Transformations'].append(
-                OrderedDict(Name='Convolve', Input=trial_type_factors))
-
-        run_model = OrderedDict(X=trial_type_factors)
+        run_model['HRF'] = dict(
+            Variables=trial_type_factors,
+            Model="DoubleGamma",
+            Parameters=dict(
+                PeakDelay=3,
+                PeakDispersion=6,
+                UndershootDelay=10,
+                UndershootDispersion=12,
+                PeakUndershootRatio=0.2
+            )
+        )
         run["Model"] = run_model
 
         if one_vs_rest:
@@ -107,19 +132,19 @@ def auto_model(layout, scan_length=None, one_vs_rest=False):
                 # get contrasts names from previous block
                 contrast_names = [cc["Name"] for cc in nodes[-1]["Contrasts"]]
                 nodes.append(_make_passthrough_contrast(
-                    "Session", contrast_names, "FEMA"))
+                    "Session", contrast_names, "meta"))
 
             subjects = layout.get_subjects()
             if len(subjects) > 1:
                 # get contrasts names from previous block
                 contrast_names = [cc["Name"] for cc in nodes[-1]["Contrasts"]]
                 nodes.append(_make_passthrough_contrast(
-                    "Subject", contrast_names, "FEMA"))
+                    "Subject", contrast_names, "meta"))
 
             # get contrasts names from previous block
             contrast_names = [cc["Name"] for cc in nodes[-1]["Contrasts"]]
             nodes.append(_make_passthrough_contrast(
-                "Dataset", contrast_names, "t"))
+                "Dataset", contrast_names))
 
         model["Nodes"] = nodes
         task_models.append(model)
diff --git a/setup.cfg b/setup.cfg
index 6e35ac6d..3e855013 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -31,9 +31,6 @@ install_requires =
     bids-validator
     num2words
     click >=8.0
-tests_require =
-    pytest >=3.3
-    mock
 packages = find:
 include_package_data = True
 
@@ -50,6 +47,7 @@ plottings =
     %(plotting)s
 test =
     pytest >=3.3
+    bsmschema
 tests =
     %(test)s
 ci_tests =


Test patch:
diff --git a/bids/modeling/tests/test_automodel.py b/bids/modeling/tests/test_automodel.py
index f60797e5..83553cee 100644
--- a/bids/modeling/tests/test_automodel.py
+++ b/bids/modeling/tests/test_automodel.py
@@ -1,4 +1,5 @@
 from os.path import join
+from itertools import chain
 from bids.modeling.auto_model import auto_model
 from bids.modeling import BIDSStatsModelsGraph
 from bids.layout import BIDSLayout
@@ -15,15 +16,31 @@ def model():
 
     return models[0]
 
+def test_automodel_valid(model):
+    try:
+        from bsmschema.models import BIDSStatsModel
+    except ImportError:
+        pytest.skip("Needs bsmschema, available for Python 3.8+")
+    BIDSStatsModel.parse_obj(model)
 
-def test_auto_model_graph(model):
-
+def test_automodel_runs(model):
     layout_path = join(get_test_data_path(), 'ds005')
     layout = BIDSLayout(layout_path)
 
     # Test to make sure an analaysis can be setup from the generated model
     graph = BIDSStatsModelsGraph(layout, model)
-    graph.load_collections(scan_length=480)
+    graph.load_collections(scan_length=480, subject=["01", "02"])
+    outputs = graph["Run"].run()
+    assert len(outputs) == 6
+    cis = list(chain(*[op.contrasts for op in outputs]))
+    assert len(cis) == 6
+    outputs = graph["Subject"].run(cis)
+    # 2 subjects x 1 contrast
+    assert len(outputs) == 2
+    cis = list(chain(*[op.contrasts for op in outputs]))
+    assert len(cis) == 2
+
+def test_auto_model_graph(model):
 
     assert model['Name'] == 'ds005_mixedgamblestask'
 
@@ -31,7 +48,9 @@ def test_auto_model_graph(model):
     block = model['Nodes'][0]
     assert block['Name'] == 'Run'
     assert block['Level'] == 'Run'
-    assert block['Transformations'][0]['Name'] == 'Factor'
+    assert block['Model']['Type'] == 'glm'
+    assert block['Transformations']['Transformer'] == 'pybids-transforms-v1'
+    assert block['Transformations']['Instructions'][0]['Name'] == 'Factor'
     assert block['Contrasts'][0]['Name'] == 'run_parametric gain'
     assert block['Contrasts'][0]['Weights'] == [1]
     assert block['Contrasts'][0]['Test'] == 't'
@@ -40,14 +59,16 @@ def test_auto_model_graph(model):
     block = model['Nodes'][1]
     assert block['Name'] == 'Subject'
     assert block['Level'] == 'Subject'
+    assert block['Model']['Type'] == 'meta'
     assert block['Model']['X'][0] == 'run_parametric gain'
     assert block['Contrasts'][0]['Name'] == 'subject_run_parametric gain'
-    assert block['Contrasts'][0]['Test'] == 'FEMA'
+    assert block['Contrasts'][0]['Test'] == 't'
 
     # dataset level
     block = model['Nodes'][2]
     assert block['Name'] == 'Dataset'
     assert block['Level'] == 'Dataset'
+    assert block['Model']['Type'] == 'glm'
     assert block['Model']['X'][0] == 'subject_run_parametric gain'
     assert block['Contrasts'][0]['Name'] == 'dataset_subject_run_parametric gain'
     assert block['Contrasts'][0]['Test'] == 't'