示例#1
0
def test_fit_decay_ts(testdata1):
    """
    fit_decay_ts should return data in samples x time shape.
    """
    t2sv, s0v, t2svG, s0vG = me.fit_decay_ts(testdata1['data'],
                                             testdata1['tes'],
                                             testdata1['mask'],
                                             testdata1['mask_sum'])
    assert t2sv.ndim == 2
    assert s0v.ndim == 2
    assert t2svG.ndim == 2
    assert s0vG.ndim == 2
示例#2
0
def test_fit_decay_ts(testdata1):
    """
    fit_decay_ts should return data in samples x time shape.
    """
    t2sv, s0v, t2svG, s0vG = me.fit_decay_ts(
        testdata1["data"],
        testdata1["tes"],
        testdata1["mask"],
        testdata1["adaptive_mask"],
        testdata1["fittype"],
    )
    assert t2sv.ndim == 2
    assert s0v.ndim == 2
    assert t2svG.ndim == 2
    assert s0vG.ndim == 2
示例#3
0
def test_smoke_fit_decay_ts():
    """
    test_smoke_fit_decay_ts tests that the function fit_decay_ts returns reasonable
    objects with random inputs in the correct format
    """
    n_samples = 100
    n_echos = 5
    n_times = 20
    data = np.random.random((n_samples, n_echos, n_times))
    tes = np.random.random((n_echos)).tolist()
    mask = np.ones(n_samples, dtype=int)
    mask[n_samples // 2:] = 0
    adaptive_mask = np.random.randint(2, n_echos, size=(n_samples)) * mask
    fittype = 'loglin'
    t2s_limited_ts, s0_limited_ts, t2s_full_ts, s0_full_ts = me.fit_decay_ts(
        data, tes, mask, adaptive_mask, fittype)
    assert t2s_limited_ts is not None
    assert s0_limited_ts is not None
    assert t2s_full_ts is not None
    assert s0_full_ts is not None
示例#4
0
def test_smoke_fit_decay_ts():
    """
    test_smoke_fit_decay_ts tests that the function fit_decay_ts returns reasonable 
    objects with random inputs in the correct format 
    """
    n_samples = 100
    n_echos = 5
    n_times = 20
    data = np.random.random((n_samples, n_echos, n_times))
    tes = np.random.random((n_echos)).tolist()
    mask = np.random.randint(
        2, size=n_samples)  # generate binary mask of random 0s and 1s
    masksum = np.random.random((n_samples))
    fittype = 'loglin'
    t2s_limited_ts, s0_limited_ts, t2s_full_ts, s0_full_ts = me.fit_decay_ts(
        data, tes, mask, masksum, fittype)
    assert t2s_limited_ts is not None
    assert s0_limited_ts is not None
    assert t2s_full_ts is not None
    assert s0_full_ts is not None
示例#5
0
def test_smoke_fit_decay_curvefit_ts():
    """
    test_smoke_fit_decay_ts tests that the function fit_decay_ts returns reasonable 
    objects with random inputs in the correct format when using the direct
    monoexponetial approach 
    """
    n_samples = 100
    n_echos = 5
    n_times = 20
    data = np.random.random((n_samples, n_echos, n_times))
    tes = np.random.random((n_echos)).tolist()
    mask = np.random.randint(
        2, size=n_samples)  # generate binary mask of random 0s and 1s
    masksum = np.random.random((n_samples))
    fittype = 'curvefit'
    t2s_limited_ts, s0_limited_ts, t2s_full_ts, s0_full_ts = me.fit_decay_ts(
        data, tes, mask, masksum, fittype)
    assert t2s_limited_ts is not None
    assert s0_limited_ts is not None
    assert t2s_full_ts is not None
    assert s0_full_ts is not None


# TODO: BREAK AND UNIT TESTS
示例#6
0
文件: t2smap.py 项目: schudds/tedana
def t2smap_workflow(data,
                    tes,
                    out_dir='.',
                    mask=None,
                    fittype='loglin',
                    fitmode='all',
                    combmode='t2s',
                    debug=False,
                    quiet=False):
    """
    Estimate T2 and S0, and optimally combine data across TEs.

    Parameters
    ----------
    data : :obj:`str` or :obj:`list` of :obj:`str`
        Either a single z-concatenated file (single-entry list or str) or a
        list of echo-specific files, in ascending order.
    tes : :obj:`list`
        List of echo times associated with data in milliseconds.
    out_dir : :obj:`str`, optional
        Output directory.
    mask : :obj:`str`, optional
        Binary mask of voxels to include in TE Dependent ANAlysis. Must be spatially
        aligned with `data`.
    fittype : {'loglin', 'curvefit'}, optional
        Monoexponential fitting method.
        'loglin' means to use the the default linear fit to the log of
        the data.
        'curvefit' means to use a monoexponential fit to the raw data,
        which is slightly slower but may be more accurate.
    fitmode : {'all', 'ts'}, optional
        Monoexponential model fitting scheme.
        'all' means that the model is fit, per voxel, across all timepoints.
        'ts' means that the model is fit, per voxel and per timepoint.
        Default is 'all'.
    combmode : {'t2s', 'paid'}, optional
        Combination scheme for TEs: 't2s' (Posse 1999, default), 'paid' (Poser).

    Other Parameters
    ----------------
    debug : :obj:`bool`, optional
        Whether to run in debugging mode or not. Default is False.
    quiet : :obj:`bool`, optional
        If True, suppress logging/printing of messages. Default is False.

    Notes
    -----
    This workflow writes out several files, which are described below:

    ==========================    =================================================
    Filename                      Content
    ==========================    =================================================
    T2starmap.nii.gz              Limited estimated T2* 3D map or 4D timeseries.
                                  Will be a 3D map if ``fitmode`` is 'all' and a
                                  4D timeseries if it is 'ts'.
    S0map.nii.gz                  Limited S0 3D map or 4D timeseries.
    desc-full_T2starmap.nii.gz    Full T2* map/timeseries. The difference between
                                  the limited and full maps is that, for voxels
                                  affected by dropout where only one echo contains
                                  good data, the full map uses the single echo's
                                  value while the limited map has a NaN.
    desc-full_S0map.nii.gz        Full S0 map/timeseries.
    desc-optcom_bold.nii.gz       Optimally combined timeseries.
    ==========================    =================================================
    """
    out_dir = op.abspath(out_dir)
    if not op.isdir(out_dir):
        os.mkdir(out_dir)

    if debug and not quiet:
        logging.basicConfig(level=logging.DEBUG)
    elif quiet:
        logging.basicConfig(level=logging.WARNING)
    else:
        logging.basicConfig(level=logging.INFO)

    LGR.info('Using output directory: {}'.format(out_dir))

    # ensure tes are in appropriate format
    tes = [float(te) for te in tes]
    n_echos = len(tes)

    # coerce data to samples x echos x time array
    if isinstance(data, str):
        data = [data]

    LGR.info('Loading input data: {}'.format([f for f in data]))
    catd, ref_img = io.load_data(data, n_echos=n_echos)
    n_samp, n_echos, n_vols = catd.shape
    LGR.debug('Resulting data shape: {}'.format(catd.shape))

    if mask is None:
        LGR.info('Computing adaptive mask')
    else:
        LGR.info('Using user-defined mask')
    mask, masksum = utils.make_adaptive_mask(catd,
                                             mask=mask,
                                             getsum=True,
                                             threshold=1)

    LGR.info('Computing adaptive T2* map')
    if fitmode == 'all':
        (t2s_limited, s0_limited, t2s_full,
         s0_full) = decay.fit_decay(catd, tes, mask, masksum, fittype)
    else:
        (t2s_limited, s0_limited, t2s_full,
         s0_full) = decay.fit_decay_ts(catd, tes, mask, masksum, fittype)

    # set a hard cap for the T2* map/timeseries
    # anything that is 10x higher than the 99.5 %ile will be reset to 99.5 %ile
    cap_t2s = stats.scoreatpercentile(t2s_limited.flatten(),
                                      99.5,
                                      interpolation_method='lower')
    cap_t2s_sec = utils.millisec2sec(cap_t2s * 10.)
    LGR.debug('Setting cap on T2* map at {:.5f}s'.format(cap_t2s_sec))
    t2s_limited[t2s_limited > cap_t2s * 10] = cap_t2s

    LGR.info('Computing optimal combination')
    # optimally combine data
    OCcatd = combine.make_optcom(catd,
                                 tes,
                                 masksum,
                                 t2s=t2s_full,
                                 combmode=combmode)

    # clean up numerical errors
    for arr in (OCcatd, s0_limited, t2s_limited):
        np.nan_to_num(arr, copy=False)

    s0_limited[s0_limited < 0] = 0
    t2s_limited[t2s_limited < 0] = 0

    io.filewrite(utils.millisec2sec(t2s_limited),
                 op.join(out_dir, 'T2starmap.nii.gz'), ref_img)
    io.filewrite(s0_limited, op.join(out_dir, 'S0map.nii.gz'), ref_img)
    io.filewrite(utils.millisec2sec(t2s_full),
                 op.join(out_dir, 'desc-full_T2starmap.nii.gz'), ref_img)
    io.filewrite(s0_full, op.join(out_dir, 'desc-full_S0map.nii.gz'), ref_img)
    io.filewrite(OCcatd, op.join(out_dir, 'desc-optcom_bold.nii.gz'), ref_img)
示例#7
0
def t2smap_workflow(data, tes, mask=None, fitmode='all', combmode='t2s',
                    label=None, debug=False, fittype='loglin', quiet=False):
    """
    Estimate T2 and S0, and optimally combine data across TEs.

    Parameters
    ----------
    data : :obj:`str` or :obj:`list` of :obj:`str`
        Either a single z-concatenated file (single-entry list or str) or a
        list of echo-specific files, in ascending order.
    tes : :obj:`list`
        List of echo times associated with data in milliseconds.
    mask : :obj:`str`, optional
        Binary mask of voxels to include in TE Dependent ANAlysis. Must be spatially
        aligned with `data`.
    fitmode : {'all', 'ts'}, optional
        Monoexponential model fitting scheme.
        'all' means that the model is fit, per voxel, across all timepoints.
        'ts' means that the model is fit, per voxel and per timepoint.
        Default is 'all'.
    combmode : {'t2s', 'paid'}, optional
        Combination scheme for TEs: 't2s' (Posse 1999, default), 'paid' (Poser).
    label : :obj:`str` or :obj:`None`, optional
        Label for output directory. Default is None.
    fittype : {'loglin', 'curvefit'}, optional
        Monoexponential fitting method.
        'loglin' means to use the the default linear fit to the log of
        the data.
        'curvefit' means to use a monoexponential fit to the raw data,
        which is slightly slower but may be more accurate.

    Other Parameters
    ----------------
    debug : :obj:`bool`, optional
        Whether to run in debugging mode or not. Default is False.
    quiet : :obj:`bool`, optional
        If True, suppresses logging/printing of messages. Default is False.


    Notes
    -----
    This workflow writes out several files, which are written out to a folder
    named TED.[ref_label].[label] if ``label`` is provided and TED.[ref_label]
    if not. ``ref_label`` is determined based on the name of the first ``data``
    file.

    Files are listed below:

    ======================    =================================================
    Filename                  Content
    ======================    =================================================
    t2sv.nii                  Limited estimated T2* 3D map or 4D timeseries.
                              Will be a 3D map if ``fitmode`` is 'all' and a
                              4D timeseries if it is 'ts'.
    s0v.nii                   Limited S0 3D map or 4D timeseries.
    t2svG.nii                 Full T2* map/timeseries. The difference between
                              the limited and full maps is that, for voxels
                              affected by dropout where only one echo contains
                              good data, the full map uses the single echo's
                              value while the limited map has a NaN.
    s0vG.nii                  Full S0 map/timeseries.
    ts_OC.nii                 Optimally combined timeseries.
    ======================    =================================================
    """
    # ensure tes are in appropriate format
    tes = [float(te) for te in tes]
    n_echos = len(tes)

    # coerce data to samples x echos x time array
    if isinstance(data, str):
        data = [data]

    LGR.info('Loading input data: {}'.format([f for f in data]))
    catd, ref_img = io.load_data(data, n_echos=n_echos)
    n_samp, n_echos, n_vols = catd.shape
    LGR.debug('Resulting data shape: {}'.format(catd.shape))

    try:
        ref_label = op.basename(ref_img).split('.')[0]
    except (TypeError, AttributeError):
        ref_label = op.basename(str(data[0])).split('.')[0]

    if label is not None:
        out_dir = 'TED.{0}.{1}'.format(ref_label, label)
    else:
        out_dir = 'TED.{0}'.format(ref_label)
    out_dir = op.abspath(out_dir)
    if not op.isdir(out_dir):
        LGR.info('Creating output directory: {}'.format(out_dir))
        os.mkdir(out_dir)
    else:
        LGR.info('Using output directory: {}'.format(out_dir))

    if mask is None:
        LGR.info('Computing adaptive mask')
    else:
        LGR.info('Using user-defined mask')
    mask, masksum = utils.make_adaptive_mask(catd, getsum=True)

    LGR.info('Computing adaptive T2* map')
    if fitmode == 'all':
        (t2s_limited, s0_limited,
         t2ss, s0s,
         t2s_full, s0_full) = decay.fit_decay(catd, tes, mask, masksum,
                                              fittype)
    else:
        (t2s_limited, s0_limited,
         t2s_full, s0_full) = decay.fit_decay_ts(catd, tes, mask, masksum,
                                                 fittype)

    # set a hard cap for the T2* map/timeseries
    # anything that is 10x higher than the 99.5 %ile will be reset to 99.5 %ile
    cap_t2s = stats.scoreatpercentile(t2s_limited.flatten(), 99.5,
                                      interpolation_method='lower')
    LGR.debug('Setting cap on T2* map at {:.5f}'.format(cap_t2s * 10))
    t2s_limited[t2s_limited > cap_t2s * 10] = cap_t2s

    LGR.info('Computing optimal combination')
    # optimally combine data
    OCcatd = combine.make_optcom(catd, tes, mask, t2s=t2s_full,
                                 combmode=combmode)

    # clean up numerical errors
    for arr in (OCcatd, s0_limited, t2s_limited):
        np.nan_to_num(arr, copy=False)

    s0_limited[s0_limited < 0] = 0
    t2s_limited[t2s_limited < 0] = 0

    io.filewrite(t2s_limited, op.join(out_dir, 't2sv.nii'), ref_img)
    io.filewrite(s0_limited, op.join(out_dir, 's0v.nii'), ref_img)
    io.filewrite(t2s_full, op.join(out_dir, 't2svG.nii'), ref_img)
    io.filewrite(s0_full, op.join(out_dir, 's0vG.nii'), ref_img)
    io.filewrite(OCcatd, op.join(out_dir, 'ts_OC.nii'), ref_img)
示例#8
0
def t2smap_workflow(
    data,
    tes,
    out_dir=".",
    mask=None,
    prefix="",
    convention="bids",
    fittype="loglin",
    fitmode="all",
    combmode="t2s",
    debug=False,
    quiet=False,
):
    """
    Estimate T2 and S0, and optimally combine data across TEs.

    Please remember to cite [1]_.

    Parameters
    ----------
    data : :obj:`str` or :obj:`list` of :obj:`str`
        Either a single z-concatenated file (single-entry list or str) or a
        list of echo-specific files, in ascending order.
    tes : :obj:`list`
        List of echo times associated with data in milliseconds.
    out_dir : :obj:`str`, optional
        Output directory.
    mask : :obj:`str`, optional
        Binary mask of voxels to include in TE Dependent ANAlysis. Must be spatially
        aligned with `data`.
    fittype : {'loglin', 'curvefit'}, optional
        Monoexponential fitting method.
        'loglin' means to use the the default linear fit to the log of
        the data.
        'curvefit' means to use a monoexponential fit to the raw data,
        which is slightly slower but may be more accurate.
    fitmode : {'all', 'ts'}, optional
        Monoexponential model fitting scheme.
        'all' means that the model is fit, per voxel, across all timepoints.
        'ts' means that the model is fit, per voxel and per timepoint.
        Default is 'all'.
    combmode : {'t2s', 'paid'}, optional
        Combination scheme for TEs: 't2s' (Posse 1999, default), 'paid' (Poser).

    Other Parameters
    ----------------
    debug : :obj:`bool`, optional
        Whether to run in debugging mode or not. Default is False.
    quiet : :obj:`bool`, optional
        If True, suppress logging/printing of messages. Default is False.

    Notes
    -----
    This workflow writes out several files, which are described below:

    ============================= =================================================
    Filename                      Content
    ============================= =================================================
    T2starmap.nii.gz              Estimated T2* 3D map or 4D timeseries.
                                  Will be a 3D map if ``fitmode`` is 'all' and a
                                  4D timeseries if it is 'ts'.
    S0map.nii.gz                  S0 3D map or 4D timeseries.
    desc-limited_T2starmap.nii.gz Limited T2* map/timeseries. The difference between
                                  the limited and full maps is that, for voxels
                                  affected by dropout where only one echo contains
                                  good data, the full map uses the T2* estimate
                                  from the first two echos, while the limited map
                                  will have a NaN.
    desc-limited_S0map.nii.gz     Limited S0 map/timeseries. The difference between
                                  the limited and full maps is that, for voxels
                                  affected by dropout where only one echo contains
                                  good data, the full map uses the S0 estimate
                                  from the first two echos, while the limited map
                                  will have a NaN.
    desc-optcom_bold.nii.gz       Optimally combined timeseries.
    ============================= =================================================

    References
    ----------
    .. [1] DuPre, E. M., Salo, T., Ahmed, Z., Bandettini, P. A., Bottenhorn, K. L.,
           Caballero-Gaudes, C., Dowdle, L. T., Gonzalez-Castillo, J., Heunis, S.,
           Kundu, P., Laird, A. R., Markello, R., Markiewicz, C. J., Moia, S.,
           Staden, I., Teves, J. B., Uruñuela, E., Vaziri-Pashkam, M.,
           Whitaker, K., & Handwerker, D. A. (2021).
           TE-dependent analysis of multi-echo fMRI with tedana.
           Journal of Open Source Software, 6(66), 3669. doi:10.21105/joss.03669.
    """
    out_dir = op.abspath(out_dir)
    if not op.isdir(out_dir):
        os.mkdir(out_dir)

    utils.setup_loggers(quiet=quiet, debug=debug)

    LGR.info("Using output directory: {}".format(out_dir))

    # ensure tes are in appropriate format
    tes = [float(te) for te in tes]
    n_echos = len(tes)

    # coerce data to samples x echos x time array
    if isinstance(data, str):
        data = [data]

    LGR.info("Loading input data: {}".format([f for f in data]))
    catd, ref_img = io.load_data(data, n_echos=n_echos)
    io_generator = io.OutputGenerator(
        ref_img,
        convention=convention,
        out_dir=out_dir,
        prefix=prefix,
        config="auto",
        make_figures=False,
    )
    n_samp, n_echos, n_vols = catd.shape
    LGR.debug("Resulting data shape: {}".format(catd.shape))

    if mask is None:
        LGR.info("Computing adaptive mask")
    else:
        LGR.info("Using user-defined mask")
    mask, masksum = utils.make_adaptive_mask(catd,
                                             mask=mask,
                                             getsum=True,
                                             threshold=1)

    LGR.info("Computing adaptive T2* map")
    if fitmode == "all":
        (t2s_limited, s0_limited, t2s_full,
         s0_full) = decay.fit_decay(catd, tes, mask, masksum, fittype)
    else:
        (t2s_limited, s0_limited, t2s_full,
         s0_full) = decay.fit_decay_ts(catd, tes, mask, masksum, fittype)

    # set a hard cap for the T2* map/timeseries
    # anything that is 10x higher than the 99.5 %ile will be reset to 99.5 %ile
    cap_t2s = stats.scoreatpercentile(t2s_full.flatten(),
                                      99.5,
                                      interpolation_method="lower")
    cap_t2s_sec = utils.millisec2sec(cap_t2s * 10.0)
    LGR.debug("Setting cap on T2* map at {:.5f}s".format(cap_t2s_sec))
    t2s_full[t2s_full > cap_t2s * 10] = cap_t2s

    LGR.info("Computing optimal combination")
    # optimally combine data
    OCcatd = combine.make_optcom(catd,
                                 tes,
                                 masksum,
                                 t2s=t2s_full,
                                 combmode=combmode)

    # clean up numerical errors
    for arr in (OCcatd, s0_full, t2s_full):
        np.nan_to_num(arr, copy=False)

    s0_full[s0_full < 0] = 0
    t2s_full[t2s_full < 0] = 0

    io_generator.save_file(
        utils.millisec2sec(t2s_full),
        "t2star img",
    )
    io_generator.save_file(s0_full, "s0 img")
    io_generator.save_file(
        utils.millisec2sec(t2s_limited),
        "limited t2star img",
    )
    io_generator.save_file(
        s0_limited,
        "limited s0 img",
    )
    io_generator.save_file(OCcatd, "combined img")

    # Write out BIDS-compatible description file
    derivative_metadata = {
        "Name":
        "t2smap Outputs",
        "BIDSVersion":
        "1.5.0",
        "DatasetType":
        "derivative",
        "GeneratedBy": [{
            "Name":
            "tedana",
            "Version":
            __version__,
            "Description":
            ("A pipeline estimating T2* from multi-echo fMRI data and "
             "combining data across echoes."),
            "CodeURL":
            "https://github.com/ME-ICA/tedana",
        }],
    }
    io_generator.save_file(derivative_metadata, "data description json")

    LGR.info("Workflow completed")
    utils.teardown_loggers()
示例#9
0
文件: t2smap.py 项目: jbteves/tedana
def t2smap_workflow(data,
                    tes,
                    out_dir='.',
                    mask=None,
                    prefix='',
                    convention='bids',
                    fittype='loglin',
                    fitmode='all',
                    combmode='t2s',
                    debug=False,
                    quiet=False):
    """
    Estimate T2 and S0, and optimally combine data across TEs.

    Parameters
    ----------
    data : :obj:`str` or :obj:`list` of :obj:`str`
        Either a single z-concatenated file (single-entry list or str) or a
        list of echo-specific files, in ascending order.
    tes : :obj:`list`
        List of echo times associated with data in milliseconds.
    out_dir : :obj:`str`, optional
        Output directory.
    mask : :obj:`str`, optional
        Binary mask of voxels to include in TE Dependent ANAlysis. Must be spatially
        aligned with `data`.
    fittype : {'loglin', 'curvefit'}, optional
        Monoexponential fitting method.
        'loglin' means to use the the default linear fit to the log of
        the data.
        'curvefit' means to use a monoexponential fit to the raw data,
        which is slightly slower but may be more accurate.
    fitmode : {'all', 'ts'}, optional
        Monoexponential model fitting scheme.
        'all' means that the model is fit, per voxel, across all timepoints.
        'ts' means that the model is fit, per voxel and per timepoint.
        Default is 'all'.
    combmode : {'t2s', 'paid'}, optional
        Combination scheme for TEs: 't2s' (Posse 1999, default), 'paid' (Poser).

    Other Parameters
    ----------------
    debug : :obj:`bool`, optional
        Whether to run in debugging mode or not. Default is False.
    quiet : :obj:`bool`, optional
        If True, suppress logging/printing of messages. Default is False.

    Notes
    -----
    This workflow writes out several files, which are described below:

    ============================= =================================================
    Filename                      Content
    ============================= =================================================
    T2starmap.nii.gz              Estimated T2* 3D map or 4D timeseries.
                                  Will be a 3D map if ``fitmode`` is 'all' and a
                                  4D timeseries if it is 'ts'.
    S0map.nii.gz                  S0 3D map or 4D timeseries.
    desc-limited_T2starmap.nii.gz Limited T2* map/timeseries. The difference between
                                  the limited and full maps is that, for voxels
                                  affected by dropout where only one echo contains
                                  good data, the full map uses the T2* estimate
                                  from the first two echos, while the limited map
                                  will have a NaN.
    desc-limited_S0map.nii.gz     Limited S0 map/timeseries. The difference between
                                  the limited and full maps is that, for voxels
                                  affected by dropout where only one echo contains
                                  good data, the full map uses the S0 estimate
                                  from the first two echos, while the limited map
                                  will have a NaN.
    desc-optcom_bold.nii.gz       Optimally combined timeseries.
    ============================= =================================================
    """
    out_dir = op.abspath(out_dir)
    if not op.isdir(out_dir):
        os.mkdir(out_dir)

    utils.setup_loggers(quiet=quiet, debug=debug)

    LGR.info('Using output directory: {}'.format(out_dir))

    # ensure tes are in appropriate format
    tes = [float(te) for te in tes]
    n_echos = len(tes)

    # coerce data to samples x echos x time array
    if isinstance(data, str):
        data = [data]

    LGR.info('Loading input data: {}'.format([f for f in data]))
    catd, ref_img = io.load_data(data, n_echos=n_echos)
    io_generator = io.OutputGenerator(
        ref_img,
        convention=convention,
        out_dir=out_dir,
        prefix=prefix,
        config="auto",
        make_figures=False,
    )
    n_samp, n_echos, n_vols = catd.shape
    LGR.debug('Resulting data shape: {}'.format(catd.shape))

    if mask is None:
        LGR.info('Computing adaptive mask')
    else:
        LGR.info('Using user-defined mask')
    mask, masksum = utils.make_adaptive_mask(catd,
                                             mask=mask,
                                             getsum=True,
                                             threshold=1)

    LGR.info('Computing adaptive T2* map')
    if fitmode == 'all':
        (t2s_limited, s0_limited, t2s_full,
         s0_full) = decay.fit_decay(catd, tes, mask, masksum, fittype)
    else:
        (t2s_limited, s0_limited, t2s_full,
         s0_full) = decay.fit_decay_ts(catd, tes, mask, masksum, fittype)

    # set a hard cap for the T2* map/timeseries
    # anything that is 10x higher than the 99.5 %ile will be reset to 99.5 %ile
    cap_t2s = stats.scoreatpercentile(t2s_full.flatten(),
                                      99.5,
                                      interpolation_method='lower')
    cap_t2s_sec = utils.millisec2sec(cap_t2s * 10.)
    LGR.debug('Setting cap on T2* map at {:.5f}s'.format(cap_t2s_sec))
    t2s_full[t2s_full > cap_t2s * 10] = cap_t2s

    LGR.info('Computing optimal combination')
    # optimally combine data
    OCcatd = combine.make_optcom(catd,
                                 tes,
                                 masksum,
                                 t2s=t2s_full,
                                 combmode=combmode)

    # clean up numerical errors
    for arr in (OCcatd, s0_full, t2s_full):
        np.nan_to_num(arr, copy=False)

    s0_full[s0_full < 0] = 0
    t2s_full[t2s_full < 0] = 0

    io_generator.save_file(
        utils.millisec2sec(t2s_full),
        't2star img',
    )
    io_generator.save_file(s0_full, 's0 img')
    io_generator.save_file(
        utils.millisec2sec(t2s_limited),
        'limited t2star img',
    )
    io_generator.save_file(
        s0_limited,
        'limited s0 img',
    )
    io_generator.save_file(OCcatd, 'combined img')

    # Write out BIDS-compatible description file
    derivative_metadata = {
        "Name":
        "t2smap Outputs",
        "BIDSVersion":
        "1.5.0",
        "DatasetType":
        "derivative",
        "GeneratedBy": [{
            "Name":
            "tedana",
            "Version":
            __version__,
            "Description":
            ("A pipeline estimating T2* from multi-echo fMRI data and "
             "combining data across echoes."),
            "CodeURL":
            "https://github.com/ME-ICA/tedana"
        }]
    }
    io_generator.save_file(derivative_metadata, 'data description json')

    LGR.info("Workflow completed")
    utils.teardown_loggers()