From 9c661d6526d278081fe3aa6321e1539a06b58704 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 18 Aug 2023 17:54:28 -0400 Subject: [PATCH 1/4] add utility function to compute expected value --- narps_open/utils/utils.py | 50 +++++++++++++++++++++++++++++++++++++++ tests/utils/test_utils.py | 30 +++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 narps_open/utils/utils.py diff --git a/narps_open/utils/utils.py b/narps_open/utils/utils.py new file mode 100644 index 00000000..39db916f --- /dev/null +++ b/narps_open/utils/utils.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from pathlib import Path + +import pandas as pd + +# # compute euclidian distance to the indifference line defined by +# # gain twice as big as losses +# % https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line +# a = 0.5; +# b = -1; +# c = 0; +# x = onsets{iRun#.gain; +# y = onsets{iRun}.loss; +# dist = abs(a * x + b * y + c) / (a^2 + b^2)^.5; +# onsets{iRun}.EV = dist; % create an "expected value" regressor + +def compute_expected_value(onsets: dict[str, list[float]] | pd.DataFrame | str | Path): + """Compute expected value regressor for a run. + + Parameters + ---------- + onsets : dict[str, list[float]] | pd.DataFrame | str | Path + Events for a run. + Pathlike TSV file with columns 'gain' and 'loss'. + If a dict, must have keys 'gain' and 'loss'. + If a DataFrame, must have columns 'gain' and 'loss'. + + Returns + ------- + onsets : pd.DataFrame + Onsets with expected value column added. + """ + a = 0.5 + b = -1 + c = 0 + + if isinstance(onsets, (str, Path)): + onsets = pd.read_csv(onsets, sep='\t') + + if isinstance(onsets, dict): + onsets = pd.DataFrame(onsets) + + x = onsets['gain'] + y = onsets['loss'] + + dist = abs(a * x + b * y + c) / (a**2 + b**2)**.5 + onsets['EV'] = dist + + return onsets diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 74778744..aeabffa1 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -13,7 +13,12 @@ from pytest import mark +from numpy.testing import assert_array_almost_equal +import numpy as np +import pandas as pd + from narps_open.utils import show_download_progress +from narps_open.utils.utils import compute_expected_value class TestUtils: """ A class that contains all the unit tests for the utils module.""" @@ -34,3 +39,28 @@ def test_show_download_progress(capfd): # using pytest's capfd fixture to get st show_download_progress(25,50,-1) captured = capfd.readouterr() assert captured.out == 'Downloading ⣽\r' + + +def test_compute_expected_value(tmp_path): + + onsets = {'gain': [1, 2, 3], 'loss': [1, 2, 3]} + + computed = compute_expected_value(onsets=onsets) + assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) + + df = pd.DataFrame(onsets) + computed = compute_expected_value(onsets=df) + assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) + + events_tsv = tmp_path / 'events.tsv' + df.to_csv(events_tsv, sep='\t') + computed = compute_expected_value(onsets=events_tsv) + assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) + + events_tsv = str(events_tsv) + computed = compute_expected_value(onsets=events_tsv) + assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) + + + + From 58606b37cda270d30e53d529dae34a481b08bdff Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Fri, 18 Aug 2023 17:58:50 -0400 Subject: [PATCH 2/4] apply black --- narps_open/utils/utils.py | 17 +++++++++-------- tests/utils/test_utils.py | 40 +++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/narps_open/utils/utils.py b/narps_open/utils/utils.py index 39db916f..bd515e4f 100644 --- a/narps_open/utils/utils.py +++ b/narps_open/utils/utils.py @@ -15,13 +15,14 @@ # dist = abs(a * x + b * y + c) / (a^2 + b^2)^.5; # onsets{iRun}.EV = dist; % create an "expected value" regressor + def compute_expected_value(onsets: dict[str, list[float]] | pd.DataFrame | str | Path): """Compute expected value regressor for a run. - + Parameters ---------- onsets : dict[str, list[float]] | pd.DataFrame | str | Path - Events for a run. + Events for a run. Pathlike TSV file with columns 'gain' and 'loss'. If a dict, must have keys 'gain' and 'loss'. If a DataFrame, must have columns 'gain' and 'loss'. @@ -29,22 +30,22 @@ def compute_expected_value(onsets: dict[str, list[float]] | pd.DataFrame | str | Returns ------- onsets : pd.DataFrame - Onsets with expected value column added. + Onsets with expected value column added. """ a = 0.5 b = -1 c = 0 if isinstance(onsets, (str, Path)): - onsets = pd.read_csv(onsets, sep='\t') + onsets = pd.read_csv(onsets, sep="\t") if isinstance(onsets, dict): onsets = pd.DataFrame(onsets) - x = onsets['gain'] - y = onsets['loss'] + x = onsets["gain"] + y = onsets["loss"] - dist = abs(a * x + b * y + c) / (a**2 + b**2)**.5 - onsets['EV'] = dist + dist = abs(a * x + b * y + c) / (a**2 + b**2) ** 0.5 + onsets["EV"] = dist return onsets diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index aeabffa1..873d964c 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -20,47 +20,45 @@ from narps_open.utils import show_download_progress from narps_open.utils.utils import compute_expected_value + class TestUtils: - """ A class that contains all the unit tests for the utils module.""" + """A class that contains all the unit tests for the utils module.""" @staticmethod @mark.unit_test - def test_show_download_progress(capfd): # using pytest's capfd fixture to get stdout - """ Test the show_download_progress function """ + def test_show_download_progress( + capfd, + ): # using pytest's capfd fixture to get stdout + """Test the show_download_progress function""" - show_download_progress(25,1,100) + show_download_progress(25, 1, 100) captured = capfd.readouterr() - assert captured.out == 'Downloading 25 %\r' + assert captured.out == "Downloading 25 %\r" - show_download_progress(26,2,200) + show_download_progress(26, 2, 200) captured = capfd.readouterr() - assert captured.out == 'Downloading 26 %\r' + assert captured.out == "Downloading 26 %\r" - show_download_progress(25,50,-1) + show_download_progress(25, 50, -1) captured = capfd.readouterr() - assert captured.out == 'Downloading ⣽\r' + assert captured.out == "Downloading ⣽\r" def test_compute_expected_value(tmp_path): - - onsets = {'gain': [1, 2, 3], 'loss': [1, 2, 3]} + onsets = {"gain": [1, 2, 3], "loss": [1, 2, 3]} computed = compute_expected_value(onsets=onsets) - assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) df = pd.DataFrame(onsets) computed = compute_expected_value(onsets=df) - assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) - events_tsv = tmp_path / 'events.tsv' - df.to_csv(events_tsv, sep='\t') + events_tsv = tmp_path / "events.tsv" + df.to_csv(events_tsv, sep="\t") computed = compute_expected_value(onsets=events_tsv) - assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) events_tsv = str(events_tsv) computed = compute_expected_value(onsets=events_tsv) - assert_array_almost_equal(computed['EV'], [0.447, 0.894, 1.342], decimal=3) - - - - + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) From 4c2ecede1fc0f3aeab0a2085c155512410212e4e Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 21 Sep 2023 11:58:46 -0400 Subject: [PATCH 3/4] move function and add doc --- docs/pipelines.md | 10 ++++++++ narps_open/utils/__init__.py | 49 ++++++++++++++++++++++++++++++++++++ tests/utils/test_utils.py | 34 ++++++++++++------------- 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/docs/pipelines.md b/docs/pipelines.md index 086f386f..a26e613c 100644 --- a/docs/pipelines.md +++ b/docs/pipelines.md @@ -124,6 +124,16 @@ As explained before, all pipeline inherit from the `narps_open.pipelines.Pipelin * `fwhm` : full width at half maximum for the smoothing kernel (in mm) : * `tr` : repetition time of the fMRI acquisition (equals 1.0s) +## Utility functions + +You can find several utility functions in the `narps_open.utils` module. + +- raw_data_template +- fmriprep_data_template +- compute_expected_value + +Feel free to use them in your pipeline. + ## Test your pipeline First have a look at the [testing topic of the documentation](./docs/testing.md). It explains how testing works for inside project and how you should write the tests related to your pipeline. diff --git a/narps_open/utils/__init__.py b/narps_open/utils/__init__.py index 54198316..73632666 100644 --- a/narps_open/utils/__init__.py +++ b/narps_open/utils/__init__.py @@ -1,9 +1,13 @@ #!/usr/bin/python # coding: utf-8 +from __future__ import annotations """ A set of utils functions for the narps_open package """ from os.path import join, abspath, dirname, realpath +from pathlib import Path + +import pandas as pd def show_download_progress(count, block_size, total_size): """ A hook function to be passed to urllib.request.urlretrieve in order to @@ -150,3 +154,48 @@ def fmriprep_data_template() -> dict: ) return {"func_preproc": func_preproc, "confounds_file": confounds_file} + + +def compute_expected_value(onsets: dict[str, list[float]] | pd.DataFrame | str | Path): + """Compute expected value regressor for a run. + + Parameters + ---------- + onsets : dict[str, list[float]] | pd.DataFrame | str | Path + Events for a run. + Pathlike TSV file with columns 'gain' and 'loss'. + If a dict, must have keys 'gain' and 'loss'. + If a DataFrame, must have columns 'gain' and 'loss'. + + Returns + ------- + onsets : pd.DataFrame + Onsets with expected value column added. + """ + # # compute euclidian distance to the indifference line defined by + # # gain twice as big as losses + # % https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + # a = 0.5; + # b = -1; + # c = 0; + # x = onsets{iRun#.gain; + # y = onsets{iRun}.loss; + # dist = abs(a * x + b * y + c) / (a^2 + b^2)^.5; + # onsets{iRun}.EV = dist; % create an "expected value" regressor + a = 0.5 + b = -1 + c = 0 + + if isinstance(onsets, (str, Path)): + onsets = pd.read_csv(onsets, sep="\t") + + if isinstance(onsets, dict): + onsets = pd.DataFrame(onsets) + + x = onsets["gain"] + y = onsets["loss"] + + dist = abs(a * x + b * y + c) / (a**2 + b**2) ** 0.5 + onsets["EV"] = dist + + return onsets \ No newline at end of file diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 873d964c..b0b40fb0 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -14,11 +14,10 @@ from pytest import mark from numpy.testing import assert_array_almost_equal -import numpy as np import pandas as pd from narps_open.utils import show_download_progress -from narps_open.utils.utils import compute_expected_value +from narps_open.utils import compute_expected_value class TestUtils: @@ -43,22 +42,23 @@ def test_show_download_progress( captured = capfd.readouterr() assert captured.out == "Downloading ⣽\r" + @staticmethod + @mark.unit_test + def test_compute_expected_value(tmp_path): + onsets = {"gain": [1, 2, 3], "loss": [1, 2, 3]} -def test_compute_expected_value(tmp_path): - onsets = {"gain": [1, 2, 3], "loss": [1, 2, 3]} - - computed = compute_expected_value(onsets=onsets) - assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) + computed = compute_expected_value(onsets=onsets) + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) - df = pd.DataFrame(onsets) - computed = compute_expected_value(onsets=df) - assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) + df = pd.DataFrame(onsets) + computed = compute_expected_value(onsets=df) + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) - events_tsv = tmp_path / "events.tsv" - df.to_csv(events_tsv, sep="\t") - computed = compute_expected_value(onsets=events_tsv) - assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) + events_tsv = tmp_path / "events.tsv" + df.to_csv(events_tsv, sep="\t") + computed = compute_expected_value(onsets=events_tsv) + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) - events_tsv = str(events_tsv) - computed = compute_expected_value(onsets=events_tsv) - assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) + events_tsv = str(events_tsv) + computed = compute_expected_value(onsets=events_tsv) + assert_array_almost_equal(computed["EV"], [0.447, 0.894, 1.342], decimal=3) From e7d74a55fd48858ab072145c501dafc58dce3820 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Thu, 21 Sep 2023 12:06:15 -0400 Subject: [PATCH 4/4] lint --- narps_open/utils/__init__.py | 6 +++--- tests/utils/test_utils.py | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/narps_open/utils/__init__.py b/narps_open/utils/__init__.py index 2d171878..752eab4f 100644 --- a/narps_open/utils/__init__.py +++ b/narps_open/utils/__init__.py @@ -4,14 +4,14 @@ """ A set of utils functions for the narps_open package """ +from hashlib import sha256 from os import listdir -from os.path import isfile, join, abspath, dirname, realpath, splitext +from os.path import abspath, dirname, isfile, join, realpath, splitext from pathlib import Path import pandas as pd - from nibabel import load -from hashlib import sha256 + def show_download_progress(count, block_size, total_size): """ A hook function to be passed to urllib.request.urlretrieve in order to diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 26116a88..780e0e97 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -13,10 +13,11 @@ from os.path import join import pandas as pd -from pytest import mark from numpy.testing import assert_array_almost_equal +from pytest import mark -from narps_open.utils import show_download_progress, hash_image, hash_dir_images, compute_expected_value +from narps_open.utils import (compute_expected_value, hash_dir_images, + hash_image, show_download_progress) from narps_open.utils.configuration import Configuration @@ -28,8 +29,7 @@ class TestUtils: def test_show_download_progress( capfd, ): # using pytest's capfd fixture to get stdout - """Test the show_download_progress function""" - + """Test the show_download_progress function.""" show_download_progress(25, 1, 100) captured = capfd.readouterr() assert captured.out == "Downloading 25 %\r" @@ -45,6 +45,7 @@ def test_show_download_progress( @staticmethod @mark.unit_test def test_compute_expected_value(tmp_path): + """Test the compute_expected_value function.""" onsets = {"gain": [1, 2, 3], "loss": [1, 2, 3]} computed = compute_expected_value(onsets=onsets) @@ -66,8 +67,7 @@ def test_compute_expected_value(tmp_path): @staticmethod @mark.unit_test def test_hash_image(): - """ Test the hash_image function """ - + """Test the hash_image function.""" # Get test_data for hash test_image_path = join( Configuration()['directories']['test_data'], @@ -80,8 +80,7 @@ def test_hash_image(): @staticmethod @mark.unit_test def test_hash_dir_images(): - """ Test the hash_dir_images function """ - + """Test the hash_dir_images function.""" # Get test_data for hash test_path = join( Configuration()['directories']['test_data'],