Repository: ME-ICA/tedana

Issue:
Add `--dummy-scans` parameter to remove non-steady-state volumes
One step that fMRIPrep users have to deal with when using tedana is removing the non-steady-state volumes from the preprocessed BOLD and confounds before passing them into tedana. A new parameter would make it easier for users to directly run tedana on fMRIPrep derivatives.

I am ambivalent about adding a parameter to handle non-steady-state volumes, because I would prefer to leave that to a more general workflow like fMRIPost-tedana, but given that I haven't had much time to work on fMRIPost-tedana, I thought I'd float the idea for tedana.

Discussion:

Solution patch:
diff --git a/tedana/io.py b/tedana/io.py
index c92dd81..28f0272 100644
--- a/tedana/io.py
+++ b/tedana/io.py
@@ -808,7 +808,7 @@ def writeresults_echoes(data_cat, mixing, mask, component_table, io_generator):
 
 
 # File Loading Functions
-def load_data(data, n_echos=None):
+def load_data(data, n_echos=None, dummy_scans=0):
     """Coerce input `data` files to required 3D array output.
 
     Parameters
@@ -819,6 +819,8 @@ def load_data(data, n_echos=None):
     n_echos : :obj:`int`, optional
         Number of echos in provided data array. Only necessary if `data` is a single,
         z-concatenated file. Default: None
+    dummy_scans : :obj:`int`, optional
+        Number of dummy scans in the fMRI time series. Default: 0
 
     Returns
     -------
@@ -847,12 +849,21 @@ def load_data(data, n_echos=None):
             fdata = np.stack([utils.reshape_niimg(f) for f in data], axis=1)
             ref_img = check_niimg(data[0])
             ref_img.header.extensions = []
-            return np.atleast_3d(fdata), ref_img
+
+            fdata = np.atleast_3d(fdata)
+            if dummy_scans != 0:
+                fdata = fdata[..., dummy_scans:]
+
+            return fdata, ref_img
 
     # Z-concatenated file/img
     img = check_niimg(data)
     (nx, ny), nz = img.shape[:2], img.shape[2] // n_echos
     fdata = utils.reshape_niimg(img.get_fdata().reshape(nx, ny, nz, n_echos, -1, order="F"))
+
+    if dummy_scans != 0:
+        fdata = fdata[..., dummy_scans:]
+
     # create reference image
     ref_img = img.__class__(
         np.zeros((nx, ny, nz, 1)), affine=img.affine, header=img.header, extra=img.extra
diff --git a/tedana/metrics/external.py b/tedana/metrics/external.py
index fdc6ae8..ac04f51 100644
--- a/tedana/metrics/external.py
+++ b/tedana/metrics/external.py
@@ -23,7 +23,11 @@ class RegressError(Exception):
 
 
 def load_validate_external_regressors(
-    external_regressors: str, external_regressor_config: Dict, n_vols: int
+    *,
+    external_regressors: str,
+    external_regressor_config: Dict,
+    n_vols: int,
+    dummy_scans: int,
 ) -> Tuple[pd.DataFrame, Dict]:
     """Load and validate external regressors and descriptors in dictionary.
 
@@ -34,7 +38,9 @@ def load_validate_external_regressors(
     external_regressor_config: :obj:`dict`
         A dictionary with info for fitting external regressors to component time series
     n_vols: :obj:`int`
-        Number of timepoints in the fMRI time series
+        Number of timepoints in the fMRI time series.
+    dummy_scans: :obj:`int`
+        Number of dummy scans in the fMRI time series.
 
     Returns
     -------
@@ -52,14 +58,21 @@ def load_validate_external_regressors(
         raise ValueError(f"Cannot load tsv file with external regressors: {external_regressors}")
 
     external_regressor_config = validate_extern_regress(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=dummy_scans,
     )
 
     return external_regressors, external_regressor_config
 
 
 def validate_extern_regress(
-    external_regressors: pd.DataFrame, external_regressor_config: List[Dict], n_vols: int
+    *,
+    external_regressors: pd.DataFrame,
+    external_regressor_config: List[Dict],
+    n_vols: int,
+    dummy_scans: int,
 ) -> List[Dict]:
     """Confirm external regressor dictionary matches data and expands regular expressions.
 
@@ -83,7 +96,9 @@ def validate_extern_regress(
         Each element in the list is a dict defining the regressors
         and statistical models for a test.
     n_vols : :obj:`int`
-        The number of time points in the fMRI time series
+        The number of time points in the fMRI time series.
+    dummy_scans : :obj:`int`
+        The number of dummy scans in the fMRI time series.
 
     Returns
     -------
@@ -204,11 +219,20 @@ def validate_extern_regress(
                         f"{sorted(extra_names)}\n"
                     )
 
-    if len(external_regressors.index) != n_vols:
-        err_msg += (
-            f"External Regressors have {len(external_regressors.index)} timepoints "
-            f"while fMRI data have {n_vols} timepoints\n"
-        )
+    if len(external_regressors.index) != (n_vols - dummy_scans):
+        if len(external_regressors.index) == n_vols:
+            LGR.warning(
+                "External regressors have the same number of timepoints as the fMRI data, "
+                "but dummy scans are included in the fMRI data. "
+                "tedana will remove the dummy scans from the external regressors."
+            )
+            external_regressors = external_regressors.iloc[dummy_scans:]
+        else:
+            err_msg += (
+                f"External regressors have {len(external_regressors.index)} timepoints "
+                f"while fMRI data have {n_vols} timepoints, of which {dummy_scans} are dummy "
+                "scans.\n"
+            )
 
     if err_msg:
         raise RegressError(err_msg)
diff --git a/tedana/workflows/ica_reclassify.py b/tedana/workflows/ica_reclassify.py
index c5c37be..a7febdb 100644
--- a/tedana/workflows/ica_reclassify.py
+++ b/tedana/workflows/ica_reclassify.py
@@ -114,6 +114,13 @@ def _get_parser():
         help=("Filenaming convention. bids will use the latest BIDS derivatives version."),
         default="bids",
     )
+    optional.add_argument(
+        "--dummy-scans",
+        dest="dummy_scans",
+        type=int,
+        help="Number of dummy scans to remove from the beginning of the data.",
+        default=0,
+    )
     optional.add_argument(
         "--tedort",
         dest="tedort",
@@ -205,6 +212,7 @@ def _main(argv=None):
         config=args.config,
         prefix=args.prefix,
         convention=args.convention,
+        dummy_scans=args.dummy_scans,
         tedort=args.tedort,
         gscontrol=args.gscontrol,
         no_reports=args.no_reports,
@@ -227,6 +235,7 @@ def ica_reclassify_workflow(
     config="auto",
     convention="bids",
     prefix="",
+    dummy_scans=0,
     tedort=False,
     gscontrol=None,
     no_reports=False,
@@ -258,6 +267,8 @@ def ica_reclassify_workflow(
         Will be applied to all listed rejected components, even if they were already rejected.
     out_dir : :obj:`str`, optional
         Output directory.
+    dummy_scans : :obj:`int`, optional
+        Number of dummy scans to remove from the beginning of the data. Default is 0.
     tedort : :obj:`bool`, optional
         Orthogonalize rejected components w.r.t. accepted ones prior to
         denoising. Default is False.
@@ -403,7 +414,7 @@ def ica_reclassify_workflow(
         LGR.debug("Loading input 4D data")
         data_cat = ioh.get_file_contents("input img")
         # Extract the data from the nibabel objects
-        data_cat, _ = io.load_data(data_cat, n_echos=len(data_cat))
+        data_cat, _ = io.load_data(data_cat, n_echos=len(data_cat), dummy_scans=dummy_scans)
 
     io_generator = io.OutputGenerator(
         data_optcom,
diff --git a/tedana/workflows/t2smap.py b/tedana/workflows/t2smap.py
index f90aab4..e2e7f73 100644
--- a/tedana/workflows/t2smap.py
+++ b/tedana/workflows/t2smap.py
@@ -85,6 +85,13 @@ def _get_parser():
         help=("Filenaming convention. bids will use the latest BIDS derivatives version."),
         default="bids",
     )
+    optional.add_argument(
+        "--dummy-scans",
+        dest="dummy_scans",
+        type=int,
+        help="Number of dummy scans to remove from the beginning of the data.",
+        default=0,
+    )
     optional.add_argument(
         "--masktype",
         dest="masktype",
@@ -191,6 +198,7 @@ def t2smap_workflow(
     mask=None,
     prefix="",
     convention="bids",
+    dummy_scans=0,
     masktype=["dropout"],
     fittype="loglin",
     fitmode="all",
@@ -222,6 +230,8 @@ def t2smap_workflow(
     mask : :obj:`str`, optional
         Binary mask of voxels to include in TE Dependent ANAlysis. Must be spatially
         aligned with `data`.
+    dummy_scans : :obj:`int`, optional
+        Number of dummy scans to remove from the beginning of the data. Default is 0.
     masktype : :obj:`list` with 'dropout' and/or 'decay' or None, optional
         Method(s) by which to define the adaptive mask. Default is ["dropout"].
     fittype : {'loglin', 'curvefit'}, optional
@@ -314,7 +324,7 @@ def t2smap_workflow(
         data = [data]
 
     LGR.info(f"Loading input data: {[f for f in data]}")
-    data_cat, ref_img = io.load_data(data, n_echos=n_echos)
+    data_cat, ref_img = io.load_data(data, n_echos=n_echos, dummy_scans=dummy_scans)
     io_generator = io.OutputGenerator(
         ref_img,
         convention=convention,
diff --git a/tedana/workflows/tedana.py b/tedana/workflows/tedana.py
index d9a296c..5611059 100644
--- a/tedana/workflows/tedana.py
+++ b/tedana/workflows/tedana.py
@@ -122,6 +122,13 @@ def _get_parser():
         help=("Filenaming convention. bids will use the latest BIDS derivatives version."),
         default="bids",
     )
+    optional.add_argument(
+        "--dummy-scans",
+        dest="dummy_scans",
+        type=int,
+        help="Number of dummy scans to remove from the beginning of the data.",
+        default=0,
+    )
     optional.add_argument(
         "--masktype",
         dest="masktype",
@@ -399,6 +406,7 @@ def tedana_workflow(
     mask=None,
     convention="bids",
     prefix="",
+    dummy_scans=0,
     masktype=["dropout"],
     fittype="loglin",
     combmode="t2s",
@@ -453,6 +461,10 @@ def tedana_workflow(
     prefix : :obj:`str` or None, optional
         Prefix for filenames generated.
         Default is ""
+    dummy_scans : :obj:`int`, optional
+        Number of dummy scans to remove from the beginning of the data
+        (both in the BOLD data and in any confounds).
+        Default is 0.
     masktype : :obj:`list` with 'dropout' and/or 'decay' or None, optional
         Method(s) by which to define the adaptive mask. Default is ["dropout"].
     fittype : {'loglin', 'curvefit'}, optional
@@ -616,7 +628,7 @@ def tedana_workflow(
     selector = ComponentSelector(tree, out_dir)
 
     LGR.info(f"Loading input data: {[f for f in data]}")
-    data_cat, ref_img = io.load_data(data, n_echos=n_echos)
+    data_cat, ref_img = io.load_data(data, n_echos=n_echos, dummy_scans=dummy_scans)
 
     # Load external regressors if provided
     # Decided to do the validation here so that, if there are issues, an error
@@ -627,7 +639,10 @@ def tedana_workflow(
     ):
         external_regressors, selector.tree["external_regressor_config"] = (
             metrics.external.load_validate_external_regressors(
-                external_regressors, selector.tree["external_regressor_config"], data_cat.shape[2]
+                external_regressors=external_regressors,
+                external_regressor_config=selector.tree["external_regressor_config"],
+                n_vols=data_cat.shape[2],
+                dummy_scans=dummy_scans,
             )
         )
 


Test patch:
diff --git a/tedana/tests/test_external_metrics.py b/tedana/tests/test_external_metrics.py
index e8fcdb0..806a50e 100644
--- a/tedana/tests/test_external_metrics.py
+++ b/tedana/tests/test_external_metrics.py
@@ -187,7 +187,10 @@ def test_validate_extern_regress_succeeds(caplog):
     external_regressors, n_vols = sample_external_regressors()
     external_regressor_config = sample_external_regressor_config()
     external_regressor_config_expanded = external.validate_extern_regress(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
     )
 
     # The regex patterns should have been replaced with the full names of the regressors
@@ -215,14 +218,22 @@ def test_validate_extern_regress_succeeds(caplog):
     # Shouldn't change anything, but making sure it runs
     caplog.clear()
     external_regressor_config_expanded = external.validate_extern_regress(
-        external_regressors, external_regressor_config_expanded, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config_expanded,
+        n_vols=n_vols,
+        dummy_scans=0,
     )
     assert "WARNING" not in caplog.text
 
     # Removing all partial model and task_keep stuff to confirm it still runs, but with a warning
     caplog.clear()
     external_regressor_config = sample_external_regressor_config("no_task_partial")
-    external.validate_extern_regress(external_regressors, external_regressor_config, n_vols)
+    external.validate_extern_regress(
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
+    )
     assert (
         "User-provided external_regressors include columns not used "
         "in any external regressor model: ['Signal']"
@@ -231,9 +242,36 @@ def test_validate_extern_regress_succeeds(caplog):
     # Add "CSF" to "Motion" partial model (also in "CSF" partial model) to test if warning appears
     caplog.clear()
     external_regressor_config = sample_external_regressor_config("csf_in_mot")
-    external.validate_extern_regress(external_regressors, external_regressor_config, n_vols)
+    external.validate_extern_regress(
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
+    )
     assert "['CSF'] used in more than one partial regressor model for nuisance" in caplog.text
 
+    # Check that dummy scans are removed from the external regressors
+    caplog.clear()
+    external_regressor_config = sample_external_regressor_config("valid")
+    external.validate_extern_regress(
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=5,
+    )
+    assert "External regressors have the same number of timepoints" in caplog.text
+
+    # Check that dummy scans are removed from the external regressors
+    caplog.clear()
+    external_regressor_config = sample_external_regressor_config("valid")
+    external.validate_extern_regress(
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols + 5,
+        dummy_scans=5,
+    )
+    assert "External regressors have the same number of timepoints" not in caplog.text
+
 
 def test_validate_extern_regress_fails():
     """Test validate_extern_regress fails when expected."""
@@ -246,7 +284,10 @@ def test_validate_extern_regress_fails():
         external.RegressError, match=f"while fMRI data have {n_vols - 1} timepoints"
     ):
         external.validate_extern_regress(
-            external_regressors, external_regressor_config, n_vols - 1
+            external_regressors=external_regressors,
+            external_regressor_config=external_regressor_config,
+            n_vols=n_vols - 1,
+            dummy_scans=0,
         )
 
     # If no external regressor labels match the regex label in config
@@ -259,7 +300,12 @@ def test_validate_extern_regress_fails():
             )
         ),
     ):
-        external.validate_extern_regress(external_regressors, external_regressor_config, n_vols)
+        external.validate_extern_regress(
+            external_regressors=external_regressors,
+            external_regressor_config=external_regressor_config,
+            n_vols=n_vols,
+            dummy_scans=0,
+        )
 
     # If Signal is in a partial model, but not "regressors" for the full model
     external_regressor_config = sample_external_regressor_config("signal_in_mot")
@@ -272,7 +318,12 @@ def test_validate_extern_regress_fails():
             )
         ),
     ):
-        external.validate_extern_regress(external_regressors, external_regressor_config, n_vols)
+        external.validate_extern_regress(
+            external_regressors=external_regressors,
+            external_regressor_config=external_regressor_config,
+            n_vols=n_vols,
+            dummy_scans=0,
+        )
 
     # If a regressor expected in the config is not in external_regressors
     # Run successfully to expand Motion labels in config and then create error
@@ -280,7 +331,10 @@ def test_validate_extern_regress_fails():
     external_regressor_config = sample_external_regressor_config()
     external_regressors, n_vols = sample_external_regressors()
     external_regressor_config_expanded = external.validate_extern_regress(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
     )
     external_regressors, n_vols = sample_external_regressors("no_mot_y_column")
     # The same error message will appear twice.
@@ -293,7 +347,23 @@ def test_validate_extern_regress_fails():
         ),
     ):
         external.validate_extern_regress(
-            external_regressors, external_regressor_config_expanded, n_vols
+            external_regressors=external_regressors,
+            external_regressor_config=external_regressor_config_expanded,
+            n_vols=n_vols,
+            dummy_scans=0,
+        )
+
+    # If there is a mismatch in the expected number of volumes, number of dummy scans, and actual
+    # number of volumes
+    with pytest.raises(
+        external.RegressError,
+        match=re.escape("External regressors have 75 timepoints while fMRI data have"),
+    ):
+        external.validate_extern_regress(
+            external_regressors=external_regressors,
+            external_regressor_config=external_regressor_config_expanded,
+            n_vols=n_vols - 6,
+            dummy_scans=5,
         )
 
 
@@ -310,7 +380,10 @@ def test_load_validate_external_regressors_fails():
         ValueError, match=f"Cannot load tsv file with external regressors: {external_regressors}"
     ):
         external.load_validate_external_regressors(
-            external_regressors, external_regressor_config, 200
+            external_regressors=external_regressors,
+            external_regressor_config=external_regressor_config,
+            n_vols=200,
+            dummy_scans=0,
         )
 
 
@@ -324,7 +397,19 @@ def test_load_validate_external_regressors_smoke():
     # Not testing outputs because this is just calling validate_extern_regress and
     # outputs are checked in those tests
     external.load_validate_external_regressors(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
+    )
+
+    # Not testing outputs because this is just calling validate_extern_regress and
+    # outputs are checked in those tests
+    external.load_validate_external_regressors(
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=5,
     )
 
 
@@ -339,7 +424,10 @@ def test_fit_regressors(caplog):
     external_regressors, n_vols = sample_external_regressors()
     external_regressor_config = sample_external_regressor_config()
     external_regressor_config_expanded = external.validate_extern_regress(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
     )
     mixing = sample_mixing_matrix()
 
@@ -425,7 +513,10 @@ def test_fit_mixing_to_regressors(caplog):
     external_regressors, n_vols = sample_external_regressors()
     external_regressor_config = sample_external_regressor_config()
     external_regressor_config_expanded = external.validate_extern_regress(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
     )
     mixing = sample_mixing_matrix()
 
@@ -529,7 +620,10 @@ def test_build_fstat_regressor_models(caplog):
     external_regressors, n_vols = sample_external_regressors()
     external_regressor_config = sample_external_regressor_config()
     external_regressor_config_expanded = external.validate_extern_regress(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
     )
 
     detrend_regressors = sample_detrend_regressors(n_vols, dtrank=3)
diff --git a/tedana/tests/test_io.py b/tedana/tests/test_io.py
index 4af9f05..7ca1737 100644
--- a/tedana/tests/test_io.py
+++ b/tedana/tests/test_io.py
@@ -18,7 +18,7 @@ data_dir = get_test_data_path()
 
 
 def test_new_nii_like():
-    data, ref = me.load_data(fnames, n_echos=len(tes))
+    data, ref = me.load_data(fnames, n_echos=len(tes), dummy_scans=0)
     nimg = me.new_nii_like(ref, data)
 
     assert isinstance(nimg, nib.Nifti1Image)
@@ -30,25 +30,31 @@ def test_load_data():
     exp_shape = (64350, 3, 5)
 
     # list of filepath to images
-    d, ref = me.load_data(fnames, n_echos=len(tes))
+    d, ref = me.load_data(fnames, n_echos=len(tes), dummy_scans=0)
     assert d.shape == exp_shape
     assert isinstance(ref, nib.Nifti1Image)
     assert np.allclose(ref.get_fdata(), nib.load(fnames[0]).get_fdata())
 
     # list of filepath to images *without n_echos*
-    d, ref = me.load_data(fnames)
+    d, ref = me.load_data(fnames, dummy_scans=0)
     assert d.shape == exp_shape
     assert isinstance(ref, nib.Nifti1Image)
     assert np.allclose(ref.get_fdata(), nib.load(fnames[0]).get_fdata())
 
+    # list of filepath to images *with dummy scans*
+    d, ref = me.load_data(fnames, dummy_scans=1)
+    assert d.shape == (exp_shape[0], exp_shape[1], exp_shape[2] - 1)
+    assert isinstance(ref, nib.Nifti1Image)
+    assert np.allclose(ref.get_fdata(), nib.load(fnames[0]).get_fdata())
+
     # list of img_like
-    d, ref = me.load_data(fimg, n_echos=len(tes))
+    d, ref = me.load_data(fimg, n_echos=len(tes), dummy_scans=0)
     assert d.shape == exp_shape
     assert isinstance(ref, nib.Nifti1Image)
     assert ref == fimg[0]
 
     # list of img_like *without n_echos*
-    d, ref = me.load_data(fimg)
+    d, ref = me.load_data(fimg, dummy_scans=0)
     assert d.shape == exp_shape
     assert isinstance(ref, nib.Nifti1Image)
     assert ref == fimg[0]
@@ -62,35 +68,35 @@ def test_load_data():
     # unsupported tuple of img_like
     fimg_tuple = tuple(fimg)
     with pytest.raises(TypeError):
-        d, ref = me.load_data(fimg_tuple, n_echos=len(tes))
+        d, ref = me.load_data(fimg_tuple, n_echos=len(tes), dummy_scans=0)
 
     # tuple of img_like *without n_echos*
     with pytest.raises(TypeError):
-        d, ref = me.load_data(fimg_tuple)
+        d, ref = me.load_data(fimg_tuple, dummy_scans=0)
 
     # two echos should raise value error
     with pytest.raises(ValueError):
-        me.load_data(fnames[:2])
+        me.load_data(fnames[:2], dummy_scans=0)
 
     # imagine z-cat img
-    d, ref = me.load_data(fnames[0], n_echos=3)
+    d, ref = me.load_data(fnames[0], n_echos=3, dummy_scans=0)
     assert d.shape == (21450, 3, 5)
     assert isinstance(ref, nib.Nifti1Image)
     assert ref.shape == (39, 50, 11, 1)
 
     # z-cat without n_echos should raise an error
     with pytest.raises(ValueError):
-        me.load_data(fnames[0])
+        me.load_data(fnames[0], dummy_scans=0)
 
     # imagine z-cat img in list
-    d, ref = me.load_data(fnames[:1], n_echos=3)
+    d, ref = me.load_data(fnames[:1], n_echos=3, dummy_scans=0)
     assert d.shape == (21450, 3, 5)
     assert isinstance(ref, nib.Nifti1Image)
     assert ref.shape == (39, 50, 11, 1)
 
     # z-cat in list without n_echos should raise an error
     with pytest.raises(ValueError):
-        me.load_data(fnames[:1])
+        me.load_data(fnames[:1], dummy_scans=0)
 
 
 # SMOKE TESTS
diff --git a/tedana/tests/test_metrics.py b/tedana/tests/test_metrics.py
index 4c8ad03..c4c1fae 100644
--- a/tedana/tests/test_metrics.py
+++ b/tedana/tests/test_metrics.py
@@ -72,7 +72,10 @@ def test_smoke_generate_metrics(testdata1):
 
     external_regressor_config = sample_external_regressor_config()
     external_regressor_config_expanded = external.validate_extern_regress(
-        external_regressors, external_regressor_config, n_vols
+        external_regressors=external_regressors,
+        external_regressor_config=external_regressor_config,
+        n_vols=n_vols,
+        dummy_scans=0,
     )
 
     component_table, new_mixing = collect.generate_metrics(