Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document decision tree steps in report #959

Merged
merged 30 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b50cb4e
Add comments to the minimal decision tree.
tsalo Aug 3, 2023
4d0a141
Improve comments.
tsalo Aug 3, 2023
eb4d341
Move from comments to log_extra_report.
tsalo Aug 3, 2023
d5c5b38
Add comments and fix one description.
tsalo Aug 3, 2023
61476e8
Update minimal.json
tsalo Aug 3, 2023
05d70f7
Retain artifacts.
tsalo Aug 3, 2023
b7886fe
Store correct artifact path.
tsalo Aug 3, 2023
62dc400
Test minimal decision tree.
tsalo Aug 3, 2023
7c7517f
Add newlines.
tsalo Aug 3, 2023
d35cfe6
Update tedana.py
tsalo Aug 3, 2023
daae14d
Update tedana.py
tsalo Aug 3, 2023
cb55ddd
Keep documenting the trees.
tsalo Aug 3, 2023
9e41d92
Update minimal.json
tsalo Aug 3, 2023
3d9f9fc
Fix decision tree description.
tsalo Aug 3, 2023
b10b70a
Merge remote-tracking branch 'upstream/main' into doc-tree
tsalo Aug 11, 2023
a500b05
Merge remote-tracking branch 'upstream/main' into doc-tree
tsalo Aug 12, 2023
f92d600
Document the remaining nodes in the kundu tree.
tsalo Aug 12, 2023
9b391e3
Fix manrej typo.
tsalo Aug 28, 2023
044e223
Cite Olafsson paper for decision tree nodes.
tsalo Sep 8, 2023
3ce5aca
Merge branch 'ME-ICA:main' into doc-tree-dh
handwerkerd Oct 12, 2023
5d0947d
updated minimal.json and started references.bib
handwerkerd Oct 20, 2023
b9aaa5d
Merge 'upstream/main' into doc-tree-dh
handwerkerd Oct 27, 2023
3875ea7
Merge branch 'main' into doc-tree
tsalo Oct 30, 2023
7a6895c
aligning to main again
handwerkerd Nov 1, 2023
7cee859
test-component_selector
handwerkerd Nov 1, 2023
262f62b
Merge branch 'doc-tree' into doc-tree-dh
tsalo Feb 3, 2024
2e4f290
Merge pull request #12 from handwerkerd/doc-tree-dh
tsalo Feb 3, 2024
ded2abe
Merge remote-tracking branch 'upstream/main' into doc-tree-dh
handwerkerd Feb 20, 2024
8d4195b
Merge pull request #15 from handwerkerd/doc-tree-dh
tsalo Feb 20, 2024
fb4c290
Merge remote-tracking branch 'upstream/main' into doc-tree
tsalo Feb 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 83 additions & 35 deletions tedana/resources/decision_trees/kundu.json

Large diffs are not rendered by default.

100 changes: 55 additions & 45 deletions tedana/resources/decision_trees/minimal.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"tree_id": "minimal_decision_tree_test1",
"info": "Proposed minimal decision tree",
"report": "This is based on the minimal criteria of the original MEICA decision tree \\citep{kundu2013integrated} without the more aggressive noise removal steps \\citep{dupre2021te}.",
"tree_id": "minimal_decision_tree",
"info": "first version of minimal decision tree",
"report": "The minimal decision tree \\citep{tedana_decision_trees_Oct2023} is a simplified version of the MEICA decision tree \\citep{kundu2013integrated, dupre2021te} without many criteria that do not rely on kappa and rho thresholds. ",
"necessary_metrics": [
"kappa",
"rho",
Expand All @@ -21,6 +21,7 @@
"Unlikely BOLD",
"Low variance"
],
"_comment": "More information on the minimial decision tree and how it differs from other options is at https://tedana.readthedocs.io/en/stable/included_decision_trees.html. Descriptions of the metrics used are in desc-tedana.metrics.json, which is ouputted when this tree is run",
"nodes": [
{
"functionname": "manual_classify",
Expand All @@ -29,10 +30,11 @@
"decide_comps": "all"
},
"kwargs": {
"log_extra_report": "",
"clear_classification_tags": true,
"dont_warn_reclassify": true
}
"dont_warn_reclassify": true,
"log_extra_info": ""
},
"_comment": "All components are initially labeled as 'unclassified'."
},
{
"functionname": "dec_left_op_right",
Expand All @@ -45,9 +47,10 @@
"right": "kappa"
},
"kwargs": {
"log_extra_report": "",
"tag_if_true": "Unlikely BOLD"
}
"tag_if_true": "Unlikely BOLD",
"log_extra_info": ""
},
"_comment": "The first four steps are for rejecting components that very unlikely to have substantial T2* signal. Any components with rho greater than kappa are rejected. Higher rho than kappa means that the component better fits the TE-independence (S0) model than the TE-dependence (T2*) model."
},
{
"functionname": "dec_left_op_right",
Expand All @@ -63,17 +66,22 @@
"left2": "countsigFT2",
"op2": ">",
"right2": 0,
"log_extra_report": "",
"tag_if_true": "Unlikely BOLD"
}
"tag_if_true": "Unlikely BOLD",
"log_extra_info": ""
},
"_comment": "Any components with more voxels that are significant based on the S0 model's F-statistics than the T2* model's are rejected, as long as there is at least one significant voxel for the T2 model."
},
{
"functionname": "calc_median",
"parameters": {
"decide_comps": "all",
"metric_name": "variance explained",
"median_label": "varex"
}
},
"kwargs": {
"log_extra_info": ""
},
"_comment": "The median variance explained is calculated across all components, for use in later steps."
},
{
"functionname": "dec_left_op_right",
Expand All @@ -89,9 +97,10 @@
"left2": "variance explained",
"op2": ">",
"right2": "median_varex",
"log_extra_report": "",
"tag_if_true": "Unlikely BOLD"
}
"tag_if_true": "Unlikely BOLD",
"log_extra_info": ""
},
"_comment": "Any components with higher S0 model beta map-F-statistic map Dice similarity index than T2 model beta map-F-statistic map Dice similarity index and greater than median variance explained are rejected. In slightly plainer English, this step rejects any high-variance components where significant voxels in the F-stat map overlap more with highly S0-associated voxels than T2*-associated voxels."
},
{
"functionname": "dec_left_op_right",
Expand All @@ -107,20 +116,20 @@
"left2": "variance explained",
"op2": ">",
"right2": "median_varex",
"log_extra_report": "",
"tag_if_true": "Unlikely BOLD"
}
"tag_if_true": "Unlikely BOLD",
"log_extra_info": ""
},
"_comment": "Any components with a negative t-statistic comparing the distribution of T2* model F-statistics from voxels in clusters to those of voxels not in clusters and variance explained greater than median are rejected. That is reject any high-variance components exhibiting more 'speckled' T2*-associated voxels than 'clustered' ones."
},
{
"functionname": "calc_kappa_elbow",
tsalo marked this conversation as resolved.
Show resolved Hide resolved
"parameters": {
"decide_comps": "all"
},
"kwargs": {
"log_extra_info": "",
"log_extra_report": ""
"log_extra_info": ""
},
"_comment": ""
"_comment": "The kappa elbow is calculated from all components, for use in later steps."
},
{
"functionname": "calc_rho_elbow",
Expand All @@ -130,10 +139,9 @@
"kwargs": {
"subset_decide_comps": "unclassified",
"rho_elbow_type": "liberal",
"log_extra_info": "",
"log_extra_report": ""
"log_extra_info": ""
},
"_comment": ""
"_comment": "This step determines the 'rho elbow' based on the rho values for all of the components, as well as just the unclassified components. It calculates the elbow for each set of components and then takes the maximum of the two."
},
{
"functionname": "dec_left_op_right",
Expand All @@ -146,8 +154,9 @@
"right": "kappa_elbow_kundu"
},
"kwargs": {
"log_extra_report": ""
}
"log_extra_info": ""
},
"_comment": "Any unclassified components with kappa greater than or equal to the kappa elbow are provisionally accepted. Any remaining unclassified components are provisionally rejected. Nothing is left 'unclassified'"
},
{
"functionname": "dec_left_op_right",
Expand All @@ -160,11 +169,11 @@
"right": "rho"
},
"kwargs": {
"log_extra_info": "If kappa>elbow and kappa>2*rho accept even if rho>elbow",
"log_extra_report": "",
"right_scale": 2,
"tag_if_true": "Likely BOLD"
}
"tag_if_true": "Likely BOLD",
"log_extra_info": ""
},
"_comment": "Any provisionally accepted components with kappa greater than two times rho are accepted. That is, even if a component has a high rho value, if kappa above threshold and substantially higher, assume it as something work keeping and accept it"
},
{
"functionname": "dec_left_op_right",
Expand All @@ -180,8 +189,9 @@
"right": "rho_elbow_liberal"
},
"kwargs": {
"log_extra_report": ""
}
"log_extra_info": ""
},
"_comment": "Any provisionally accepted or provisionally rejected components with rho values greater than the liberal rho elbow are provisionally rejected."
},
{
"functionname": "dec_variance_lessthan_thresholds",
Expand All @@ -192,12 +202,12 @@
},
"kwargs": {
"var_metric": "variance explained",
"log_extra_info": "",
"log_extra_report": "",
"single_comp_threshold": 0.1,
"all_comp_threshold": 1.0,
"tag_if_true": "Low variance"
}
"tag_if_true": "Low variance",
"log_extra_info": ""
},
"_comment": "This step flags remaining low-variance components (less than 0.1%) and accepts up to 1% cumulative variance across these components. This is done beacuse these components don't explain enough variance to be worth further reducing the degrees of freedom of the denoised data."
},
{
"functionname": "manual_classify",
Expand All @@ -206,10 +216,10 @@
"decide_comps": "provisionalaccept"
},
"kwargs": {
"log_extra_info": "",
"log_extra_report": "",
"tag": "Likely BOLD"
}
"tag": "Likely BOLD",
"log_extra_info": ""
},
"_comment": "All remaining provisionally accepted components are accepted."
},
{
"functionname": "manual_classify",
Expand All @@ -221,10 +231,10 @@
]
},
"kwargs": {
"log_extra_info": "",
"log_extra_report": "",
"tag": "Unlikely BOLD"
}
"tag": "Unlikely BOLD",
"log_extra_info": ""
},
"_comment": "All remaining unclassified (nothing should be unclassified) or provisionally rejected components are rejected."
}
]
}
20 changes: 20 additions & 0 deletions tedana/resources/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,23 @@ @misc{sochat2015ttoz
url = {https://doi.org/10.5281/zenodo.32508},
year = 2015
}

@article{olafsson2015enhanced,
title = {Enhanced identification of BOLD-like components with multi-echo simultaneous multi-slice (MESMS) fMRI and multi-echo ICA},
author = {Olafsson, Valur and Kundu, Prantik and Wong, Eric C and Bandettini, Peter A and Liu, Thomas T},
journal = {Neuroimage},
volume = {112},
pages = {43--51},
year = {2015},
publisher = {Elsevier},
url = {https://doi.org/10.1016/j.neuroimage.2015.02.052},
doi = {10.1016/j.neuroimage.2015.02.052}
}


@article{tedana_decision_trees_Oct2023,
title = {Decision tree specifications distributed with tedana},
journal = {temp},
url = {temp},
doi = {temp}
}
4 changes: 2 additions & 2 deletions tedana/selection/selection_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,9 +855,9 @@ def calc_rho_elbow(
"""
function_name_idx = f"Step {selector.current_node_idx}: calc_rho_elbow"

if rho_elbow_type == "kundu".lower():
if rho_elbow_type == "kundu":
elbow_name = "rho_elbow_kundu"
elif rho_elbow_type == "liberal".lower():
elif rho_elbow_type == "liberal":
elbow_name = "rho_elbow_liberal"
else:
raise ValueError(
Expand Down
6 changes: 2 additions & 4 deletions tedana/selection/tedica.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,8 @@ def automatic_selection(component_table, n_echos, n_vols, tree="kundu"):
"""
LGR.info("Performing ICA component selection with Kundu decision tree v2.5")
RepLGR.info(
"Next, component selection was performed to identify "
"BOLD (TE-dependent), non-BOLD (TE-independent), and "
"uncertain (low-variance) components using the Kundu "
"decision tree (v2.5) \\citep{kundu2013integrated}."
"\n\nNext, component selection was performed to identify BOLD (TE-dependent) and "
"non-BOLD (TE-independent) components using a decision tree."
)

component_table["classification_tags"] = ""
Expand Down
18 changes: 6 additions & 12 deletions tedana/tests/test_component_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ def test_load_config_fails():
def test_load_config_succeeds():
"""Tests to make sure load_config succeeds."""

# The minimal tree should have an id of "minimal_decision_tree_test1"
# The minimal tree should have an id of "minimal_decision_tree"
tree = component_selector.load_config("minimal")
assert tree["tree_id"] == "minimal_decision_tree_test1"
assert tree["tree_id"] == "minimal_decision_tree"


def test_minimal():
Expand Down Expand Up @@ -194,9 +194,7 @@ def test_minimal():


def test_validate_tree_succeeds():
"""
Tests to make sure validate_tree suceeds for all default
decision trees in decision trees.
"""Test to make sure validate_tree suceeds for all default trees.

Tested on all default trees in ./tedana/resources/decision_trees
Note: If there is a tree in the default trees directory that
Expand All @@ -223,20 +221,16 @@ def test_validate_tree_succeeds():


def test_validate_tree_warnings():
"""
Tests to make sure validate_tree triggers all warning conditions
but still succeeds.
"""
"""Test to make sure validate_tree triggers all warning conditions."""

# A tree that raises all possible warnings in the validator should still be valid
assert component_selector.validate_tree(dicts_to_test("valid"))


def test_validate_tree_fails():
"""
Tests to make sure validate_tree fails for invalid trees
Tests ../resources/decision_trees/invalid*.json and.
"""Test to make sure validate_tree fails for invalid trees.

Tests ../resources/decision_trees/invalid*.json and
./data/ComponentSelection/invalid*.json trees.
"""

Expand Down
9 changes: 4 additions & 5 deletions tedana/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,10 @@ def data_for_testing_info(test_dataset=str):


def download_test_data(osf_id, test_data_path):
"""
If current data is not already available, downloads tar.gz data.

stored at `https://osf.io/osf_id/download`.
"""If current data is not already available, downloads tar.gz data.

and unpacks into `out_path`.
Data are stored at `https://osf.io/osf_id/download`.
It unpacks into `out_path`.

Parameters
----------
Expand Down Expand Up @@ -262,6 +260,7 @@ def test_integration_five_echo(skip_integration):
out_dir=out_dir,
tedpca=0.95,
fittype="curvefit",
tree="minimal",
fixed_seed=49,
tedort=True,
verbose=True,
Expand Down
10 changes: 5 additions & 5 deletions tedana/tests/test_selection_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def sample_component_table(options=None):


def sample_selector(options=None):
"""
Retrieves a sample component table and initializes
a selector using that component table and the minimal tree.
"""Retrieve a sample component table and initializes a selector.

The selector uses that component table and the minimal tree.

options: Different strings will alter the selector
'provclass': Change the classifications to "provisional accept" for 4 components
Expand Down Expand Up @@ -118,8 +118,8 @@ def test_selectcomps2use_fails():


def test_comptable_classification_changer_succeeds():
"""
All conditions where comptable_classification_changer should run
"""All conditions where comptable_classification_changer should run.

Note: This confirms the function runs, but not that outputs are accurate.

Also tests conditions where the warning logger is used, but doesn't
Expand Down
12 changes: 8 additions & 4 deletions tedana/tests/test_t2smap.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
class TestT2smap:
def test_basic_t2smap1(self):
"""
A very simple test, to confirm that t2smap creates output
A very simple test, to confirm that t2smap creates output.

files.
"""
data_dir = get_test_data_path()
Expand Down Expand Up @@ -41,7 +42,8 @@ def test_basic_t2smap1(self):

def test_basic_t2smap2(self):
"""
A very simple test, to confirm that t2smap creates output
A very simple test, to confirm that t2smap creates output.

files when fitmode is set to ts.
"""
data_dir = get_test_data_path()
Expand Down Expand Up @@ -70,7 +72,8 @@ def test_basic_t2smap2(self):

def test_basic_t2smap3(self):
"""
A very simple test, to confirm that t2smap creates output
A very simple test, to confirm that t2smap creates output.

files when combmode is set to 'paid'.
"""
data_dir = get_test_data_path()
Expand Down Expand Up @@ -99,7 +102,8 @@ def test_basic_t2smap3(self):

def test_basic_t2smap4(self):
"""
A very simple test, to confirm that t2smap creates output
A very simple test, to confirm that t2smap creates output.

files when combmode is set to 'paid' and fitmode is set to 'ts'.
"""
data_dir = get_test_data_path()
Expand Down
Loading