Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e061f4c
Support array-type metadata fields.
tsalo Jan 17, 2025
ce5dea8
Try supporting lists of strings too.
tsalo Jan 17, 2025
d6b194a
Merge branch 'main' into list-metadata
tsalo Jan 17, 2025
144a492
Add test.
tsalo Jan 17, 2025
52d02d0
Update test_utils.py
tsalo Jan 17, 2025
7a546b6
Update stuff.
tsalo Jan 17, 2025
45bcc76
Merge branch 'main' into list-metadata
tsalo Jan 17, 2025
6c3f3e1
Update test_utils.py
tsalo Jan 17, 2025
7c50435
Merge branch 'main' into list-metadata
tsalo Jan 27, 2025
0460d7f
Merge branch 'main' into list-metadata
tsalo Jan 28, 2025
e1b1d9b
Merge branch 'main' into list-metadata
tsalo Feb 4, 2025
dc07c6f
Rename format_params to cluster_single_parameters.
tsalo Feb 4, 2025
e3ca352
Keep working.
tsalo Feb 4, 2025
6a5c09b
Merge branch 'main' into list-metadata
tsalo Feb 4, 2025
929bfab
Move cluster_single_parameters from cubids to utils.
tsalo Feb 4, 2025
c2265dd
Fix import.
tsalo Feb 4, 2025
71d5e73
Remove unused function.
tsalo Feb 4, 2025
3fde7ab
Update utils.py
tsalo Feb 4, 2025
10ce7c7
Update test_utils.py
tsalo Feb 4, 2025
9897b03
Update round_params too.
tsalo Feb 5, 2025
a318078
Update test.
tsalo Feb 5, 2025
ff4f5f8
Update.
tsalo Feb 5, 2025
0441113
Update.
tsalo Feb 5, 2025
6ede27a
Merge branch 'main' into list-metadata
tsalo Feb 5, 2025
aa3aa08
Merge branch 'main' into list-metadata
tsalo Feb 13, 2025
f109d70
Merge branch 'main' into list-metadata
tsalo Feb 21, 2025
d31bf32
Fix possible bug from #439.
tsalo Feb 21, 2025
e6d8d35
Merge branch 'main' into list-metadata
tsalo Feb 26, 2025
ed4c3cf
Allow ndarray metadata.
tsalo Mar 3, 2025
c8986f9
Add ImageOrientationPatientDICOM, remove Obliquity
tsalo Mar 3, 2025
b31ee74
Remove obliquity mentions.
tsalo Mar 3, 2025
f52a419
Merge branch 'main' into list-metadata
tsalo Mar 3, 2025
fbbdd26
Merge branch 'main' into list-metadata
tsalo Apr 3, 2025
35e1847
Merge branch 'main' into list-metadata
tsalo Apr 4, 2025
0034af1
Revert obliquity-related changes.
tsalo Apr 4, 2025
43171f1
Update test_bond.py
tsalo Apr 4, 2025
85348ea
Update example.rst
tsalo Apr 4, 2025
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
30 changes: 0 additions & 30 deletions cubids/tests/test_cubids.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,21 +449,6 @@ def _test__get_param_groups(cubids_instance):
# Add assertions here


def _test_round_params(cubids_instance):
"""Test rounding parameters.
Parameters
----------
cubids_instance : CuBIDS
An instance of the CuBIDS class.
"""
param_group_df = pd.DataFrame({"param": [0.123456789]})
config = {"param": {"round": 3}}
modality = "bold"
rounded_params = cubids_instance.round_params(param_group_df, config, modality)
# Add assertions here


def _test_get_sidecar_metadata(cubids_instance):
"""Test getting sidecar metadata.
Expand All @@ -477,21 +462,6 @@ def _test_get_sidecar_metadata(cubids_instance):
# Add assertions here


def _test_format_params(cubids_instance):
"""Test formatting parameters.
Parameters
----------
cubids_instance : CuBIDS
An instance of the CuBIDS class.
"""
param_group_df = pd.DataFrame({"param": [0.123456789]})
config = {"param": {"format": "{:.2f}"}}
modality = "bold"
formatted_params = cubids_instance.format_params(param_group_df, config, modality)
# Add assertions here


def _test__order_columns(cubids_instance):
"""Test ordering columns.
Expand Down
184 changes: 184 additions & 0 deletions cubids/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
"""Tests for the utils module."""

import pandas as pd

from cubids import utils
from cubids.tests.utils import compare_group_assignments


def test_round_params():
"""Test the cubids.utils.round_params function."""
# Example DataFrame
df = pd.DataFrame(
{
"A": [1.12345, 2.23456, 3.34567],
"B": [[1.12345, 2.23456], [3.34567, 4.45678], [5.56789, 6.67890]],
"C": ["text", "more text", "even more text"],
"D": [1.12345, 2.23456, 3.34567],
}
)

# Example config
config = {
"sidecar_params": {
"func": {
"A": {"precision": 2},
"B": {"precision": 2},
},
},
"derived_params": {
"func": {},
},
}

# Expected DataFrame after rounding
expected_df = pd.DataFrame(
{
"A": [1.12, 2.23, 3.35],
"B": [[1.12, 2.23], [3.35, 4.46], [5.57, 6.68]],
"C": ["text", "more text", "even more text"],
"D": [1.12345, 2.23456, 3.34567],
}
)

# Round columns
rounded_df = utils.round_params(df, config, "func")

# Assert that the rounded DataFrame matches the expected DataFrame
pd.testing.assert_frame_equal(rounded_df, expected_df)


def test_cluster_single_parameters():
"""Test the cubids.utils.cluster_single_parameters function.
We want to test that the function correctly clusters parameters based on the
configuration dictionary.
"""
config = {
"sidecar_params": {
"func": {
"RepetitionTime": {"tolerance": 0.01, "suggest_variant_rename": True},
"TaskName": {"suggest_variant_rename": True},
"SliceTiming": {"tolerance": 0.01, "suggest_variant_rename": True},
"ImageType": {"suggest_variant_rename": True},
},
},
"derived_params": {
"func": {},
},
}

# Mock up the input. The variants are explicitly prepared.
params = [
{
"RepetitionTime": 2.0,
"TaskName": "rest eyes closed",
"SliceTiming": [0.0, 1.0, 2.0],
"ImageType": ["ORIGINAL", "NONE", "M"],
},
{
"RepetitionTime": 2.0,
"TaskName": "rest eyes closed",
"SliceTiming": [0.0, 1.0, 2.0],
"ImageType": ["ORIGINAL", "NONE", "M"],
},
{
"RepetitionTime": 2.0,
# TaskName variant
"TaskName": "rest eyes open",
"SliceTiming": [0.0, 1.0, 2.0],
"ImageType": ["ORIGINAL", "NONE", "M"],
},
{
# RepetitionTime variant
"RepetitionTime": 1.9,
"TaskName": "rest eyes closed",
"SliceTiming": [0.0, 1.0, 2.0],
"ImageType": ["ORIGINAL", "NONE", "M"],
},
{
"RepetitionTime": 2.0,
"TaskName": "rest eyes closed",
# SliceTiming variant (length)
"SliceTiming": [0.0, 0.5, 1.0, 1.5, 2.0],
"ImageType": ["ORIGINAL", "NONE", "M"],
},
{
"RepetitionTime": 2.0,
"TaskName": "rest eyes closed",
# SliceTiming variant (values)
"SliceTiming": [0.0, 1.0, 1.9],
"ImageType": ["ORIGINAL", "NONE", "M"],
},
{
"RepetitionTime": 2.0,
"TaskName": "rest eyes closed",
"SliceTiming": [0.0, 1.0, 2.0],
# ImageType variant (length)
"ImageType": ["ORIGINAL", "NONE", "M", "NORM"],
},
{
"RepetitionTime": 2.0,
"TaskName": "rest eyes closed",
"SliceTiming": [0.0, 1.0, 2.0],
# ImageType variant (values)
"ImageType": ["ORIGINAL", "NONE", "P"],
},
]
files_df = pd.DataFrame(params)
modality = "func"

# Run the function
out_df = utils.cluster_single_parameters(
df=files_df,
config=config,
modality=modality,
)
assert isinstance(out_df, pd.DataFrame)
assert "Cluster_RepetitionTime" in out_df.columns
assert "Cluster_SliceTiming" in out_df.columns
assert "Cluster_ImageType" in out_df.columns
# Non-list columns without tolerance don't get clustered
assert "Cluster_TaskName" not in out_df.columns

assert compare_group_assignments(
out_df["Cluster_RepetitionTime"].values.astype(int),
[0, 0, 0, 1, 0, 0, 0, 0],
)
assert compare_group_assignments(
out_df["Cluster_SliceTiming"].values.astype(int),
[0, 0, 0, 0, 2, 1, 0, 0],
)
assert compare_group_assignments(
out_df["Cluster_ImageType"].values.astype(int),
[0, 0, 0, 0, 0, 0, 1, 2],
)

# Change the tolerance for SliceTiming
config["sidecar_params"]["func"]["SliceTiming"]["tolerance"] = 0.5
out_df = utils.cluster_single_parameters(
df=files_df,
config=config,
modality=modality,
)
assert isinstance(out_df, pd.DataFrame)
assert "Cluster_RepetitionTime" in out_df.columns
assert "Cluster_SliceTiming" in out_df.columns
assert "Cluster_ImageType" in out_df.columns
# Non-list columns without tolerance don't get clustered
assert "Cluster_TaskName" not in out_df.columns

assert compare_group_assignments(
out_df["Cluster_RepetitionTime"].values.astype(int),
[0, 0, 0, 1, 0, 0, 0, 0],
)
# Different lengths still produce different clusters,
# but the value-based variants are now the same
assert compare_group_assignments(
out_df["Cluster_SliceTiming"].values.astype(int),
[0, 0, 0, 0, 1, 0, 0, 0],
)
assert compare_group_assignments(
out_df["Cluster_ImageType"].values.astype(int),
[0, 0, 0, 0, 0, 0, 1, 2],
)
53 changes: 53 additions & 0 deletions cubids/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,56 @@ def chdir(path):
yield
finally:
os.chdir(oldpwd)


def compare_group_assignments(list1, list2):
"""Compare two lists for equality based on group assignments.
This function checks if two lists can be considered equal based on their group assignments.
The actual values in the lists do not matter, only the group assignments do. Each unique value
in the first list is mapped to a unique value in the second list, and the function checks if
this mapping is consistent throughout the lists.
Parameters
----------
list1 : list
The first list to compare.
list2 : list
The second list to compare.
Returns
-------
bool
True if the lists are equal based on group assignments, False otherwise.
Examples
--------
>>> list1 = [1, 2, 1, 3, 2]
>>> list2 = ['a', 'b', 'a', 'c', 'b']
>>> compare_group_assignments(list1, list2)
True
>>> list1 = [1, 2, 1, 3, 2]
>>> list2 = ['b', 'd', 'b', 'q', 'd']
>>> compare_group_assignments(list1, list2)
True
>>> list1 = [1, 2, 1, 3, 2]
>>> list2 = ['a', 'b', 'a', 'c', 'd']
>>> compare_group_assignments(list1, list2)
False
"""
if len(list1) != len(list2):
return False

mapping = {}
for a, b in zip(list1, list2):
if a in mapping:
if mapping[a] != b:
return False
else:
if b in mapping.values():
return False
mapping[a] = b

return True
Loading