diff --git a/tedana/selection/_utils.py b/tedana/selection/_utils.py index 3722bd991..a86a47070 100644 --- a/tedana/selection/_utils.py +++ b/tedana/selection/_utils.py @@ -41,6 +41,17 @@ def getelbow_cons(arr, return_val=False): """ if arr.ndim != 1: raise ValueError('Parameter arr should be 1d, not {0}d'.format(arr.ndim)) + + if not arr.size: + raise ValueError( + "Empty array detected during elbow calculation. " + "This error happens when getelbow_cons is incorrectly called on no components. " + "If you see this message, please open an issue at " + "https://github.com/ME-ICA/tedana/issues with the full traceback and any data " + "necessary to reproduce this error, so that we create additional data checks to " + "prevent this from happening." + ) + arr = np.sort(arr)[::-1] nk = len(arr) temp1 = [(arr[nk - 5 - ii - 1] > arr[nk - 5 - ii:nk].mean() + 2 * arr[nk - 5 - ii:nk].std()) @@ -79,6 +90,17 @@ def getelbow(arr, return_val=False): """ if arr.ndim != 1: raise ValueError('Parameter arr should be 1d, not {0}d'.format(arr.ndim)) + + if not arr.size: + raise ValueError( + "Empty array detected during elbow calculation. " + "This error happens when getelbow is incorrectly called on no components. " + "If you see this message, please open an issue at " + "https://github.com/ME-ICA/tedana/issues with the full traceback and any data " + "necessary to reproduce this error, so that we create additional data checks to " + "prevent this from happening." + ) + arr = np.sort(arr)[::-1] n_components = arr.shape[0] coords = np.array([np.arange(n_components), arr]) diff --git a/tedana/selection/tedica.py b/tedana/selection/tedica.py index 2032309e3..02c0d021c 100644 --- a/tedana/selection/tedica.py +++ b/tedana/selection/tedica.py @@ -245,13 +245,23 @@ def kundu_selection_v2(comptable, n_echos, n_vols): # Compute elbows from other elbows f05, _, f01 = getfbounds(n_echos) kappas_nonsig = comptable.loc[comptable['kappa'] < f01, 'kappa'] + if not kappas_nonsig.size: + LGR.warning( + "No nonsignificant kappa values detected. " + "Only using elbow calculated from all kappa values." + ) + kappas_nonsig_elbow = np.nan + else: + kappas_nonsig_elbow = getelbow(kappas_nonsig, return_val=True) + + kappas_all_elbow = getelbow(comptable['kappa'], return_val=True) + # NOTE: Would an elbow from all Kappa values *ever* be lower than one from - # a subset of lower values? - kappa_elbow = np.min((getelbow(kappas_nonsig, return_val=True), - getelbow(comptable['kappa'], return_val=True))) - rho_elbow = np.mean((getelbow(comptable.loc[ncls, 'rho'], return_val=True), - getelbow(comptable['rho'], return_val=True), - f05)) + # a subset of lower (i.e., nonsignificant) values? + kappa_elbow = np.nanmin((kappas_all_elbow, kappas_nonsig_elbow)) + rhos_ncls_elbow = getelbow(comptable.loc[ncls, 'rho'], return_val=True) + rhos_all_elbow = getelbow(comptable['rho'], return_val=True) + rho_elbow = np.mean((rhos_ncls_elbow, rhos_all_elbow, f05)) # Provisionally accept components based on Kappa and Rho elbows acc_prov = ncls[(comptable.loc[ncls, 'kappa'] >= kappa_elbow) & diff --git a/tedana/tests/test_selection_utils.py b/tedana/tests/test_selection_utils.py new file mode 100644 index 000000000..7288f8c6e --- /dev/null +++ b/tedana/tests/test_selection_utils.py @@ -0,0 +1,45 @@ +"""Tests for the tedana.selection._utils module.""" +import numpy as np +import pytest + +from tedana.selection import _utils + + +def test_getelbow_smoke(): + """A smoke test for the getelbow function.""" + arr = np.random.random(100) + idx = _utils.getelbow(arr) + assert isinstance(idx, np.integer) + + val = _utils.getelbow(arr, return_val=True) + assert isinstance(val, float) + + # Running an empty array should raise a ValueError + arr = np.array([]) + with pytest.raises(ValueError): + _utils.getelbow(arr) + + # Running a 2D array should raise a ValueError + arr = np.random.random((100, 100)) + with pytest.raises(ValueError): + _utils.getelbow(arr) + + +def test_getelbow_cons(): + """A smoke test for the getelbow_cons function.""" + arr = np.random.random(100) + idx = _utils.getelbow_cons(arr) + assert isinstance(idx, np.integer) + + val = _utils.getelbow_cons(arr, return_val=True) + assert isinstance(val, float) + + # Running an empty array should raise a ValueError + arr = np.array([]) + with pytest.raises(ValueError): + _utils.getelbow_cons(arr) + + # Running a 2D array should raise a ValueError + arr = np.random.random((100, 100)) + with pytest.raises(ValueError): + _utils.getelbow_cons(arr)