JALE/tests/test_input.py
2025-06-02 17:05:24 +02:00

244 lines
6.8 KiB
Python

import pathlib
from unittest.mock import patch
import numpy as np
import pandas as pd
import pytest
import jale.core.utils.input as io
# --- Test load_config ---
def test_load_config_success():
current_dir = pathlib.Path(__file__).parent
config_path = current_dir / "config.yml"
cfg = io.load_config(config_path)
assert isinstance(cfg, dict)
assert "project" in cfg and "parameters" in cfg
def test_load_config_file_not_found():
with pytest.raises(SystemExit):
io.load_config("nonexistent_file.yml")
def test_load_config_bad_yaml(tmp_path):
bad_yaml = "this: [unbalanced brackets"
file = tmp_path / "bad.yml"
file.write_text(bad_yaml)
with pytest.raises(SystemExit):
io.load_config(file)
# --- Test load_experiment_file ---
@patch("pandas.read_excel")
def test_load_experiment_file_excel(mock_read_excel):
df = pd.DataFrame(
{
"A": [1, 2, 3],
"B": [4, 5, 6],
"C": [7, 8, 9],
"D": [10, 11, 12],
"E": [13, 14, 15],
"F": [16, 17, 18],
}
)
mock_read_excel.return_value = df
result = io.load_experiment_file("file.xlsx")
assert "Articles" in result.columns
assert "x" in result.columns
assert result.shape[0] == 3
def test_load_experiment_file_unsupported_format():
with pytest.raises(SystemExit):
io.load_experiment_file("file.unsupported")
@patch("pandas.read_excel")
def test_load_experiment_file_with_bad_rows(mock_read_excel):
# DataFrame with row having only 1 or 2 non-NaN entries triggers exit
df = pd.DataFrame(
{
"A": [1, None, None],
"B": [None, None, 3],
"C": [None, None, None],
"D": [None, None, None],
"E": [None, None, None],
"F": [None, None, None],
}
)
mock_read_excel.return_value = df
with pytest.raises(SystemExit):
io.load_experiment_file("file.xlsx")
# --- Test check_coordinates_are_numbers ---
def test_check_coordinates_are_numbers_valid():
df = pd.DataFrame(
{
"x": [1.0, 2.0],
"y": [3.0, 4.0],
"z": [5.0, 6.0],
}
)
result = io.check_coordinates_are_numbers(df)
assert result.index.equals(pd.RangeIndex(start=0, stop=2))
def test_check_coordinates_are_numbers_invalid():
df = pd.DataFrame(
{
"x": [1.0, "bad"],
"y": [3.0, 4.0],
"z": [5.0, 6.0],
}
)
with pytest.raises(SystemExit):
io.check_coordinates_are_numbers(df)
# --- Test concat_tags ---
def test_concat_tags_basic():
df = pd.DataFrame(
{
"Articles": ["art1", "art2"],
"Subjects": [10, 20],
"x": [1, 2],
"y": [3, 4],
"z": [5, 6],
"CoordinateSpace": ["MNI", "TAL"],
"Tag1": ["A", None],
"Tag2": ["B", "C"],
}
)
out = io.concat_tags(df)
assert "Tags" in out.columns
assert out.loc[0, "Tags"] == ("a", "b")
assert out.loc[1, "Tags"] == ("c",)
# --- Test concat_coordinates ---
def test_concat_coordinates_pool_true():
df = pd.DataFrame(
{
"Articles": ["art1", "art1"],
"Subjects": [10, 10],
"x": [1, 2],
"y": [3, 4],
"z": [5, 6],
"CoordinateSpace": ["MNI", "MNI"],
"Tags": [("tag1",), ("tag1",)],
}
)
out = io.concat_coordinates(df, pool_experiments=True)
assert "Coordinates_mm" in out.columns
assert out.iloc[0]["NumberOfFoci"] == 2
assert isinstance(out.iloc[0]["Coordinates_mm"], np.ndarray)
def test_concat_coordinates_pool_false():
df = pd.DataFrame(
{
"Articles": ["art1", "art1"],
"Subjects": [10, 10],
"x": [1, 2],
"y": [3, 4],
"z": [5, 6],
"CoordinateSpace": ["MNI", "MNI"],
"Tags": [("tag1",), ("tag2",)],
}
)
out = io.concat_coordinates(df, pool_experiments=False)
assert out.shape[0] == 2
# --- Test convert_tal_2_mni ---
def test_convert_tal_2_mni_calls_tal2icbm_spm(monkeypatch):
df = pd.DataFrame(
{
"CoordinateSpace": ["TAL", "MNI"],
"Coordinates_mm": [np.array([[1, 2, 3]]), np.array([[4, 5, 6]])],
}
)
print("Before transform:", df.loc[0, "Coordinates_mm"])
called = {}
def fake_tal2icbm_spm(coords):
called["called"] = True
return coords + 1 # dummy transform
monkeypatch.setattr(io, "tal2icbm_spm", fake_tal2icbm_spm)
out = df.copy(deep=True)
out = io.convert_tal_2_mni(out)
print("After transform:", df.loc[0, "Coordinates_mm"])
assert called.get("called") is True
assert np.all(out.loc[0, "Coordinates_mm"] == df.loc[0, "Coordinates_mm"] + 1)
assert np.all(out.loc[1, "Coordinates_mm"] == df.loc[1, "Coordinates_mm"])
# --- Test transform_coordinates_to_voxel_space ---
def test_transform_coordinates_to_voxel_space_basic():
df = pd.DataFrame(
{"Coordinates_mm": [np.array([[0, 0, 0]]), np.array([[10, 20, 30]])]}
)
out = io.transform_coordinates_to_voxel_space(df)
assert "Coordinates" in out.columns
assert isinstance(out.iloc[0]["Coordinates"], np.ndarray)
# --- Test create_tasks_table ---
def test_create_tasks_table_basic():
df = pd.DataFrame(
{
"Tags": [("task1", "task2"), ("task1",)],
"Articles": ["art1", "art2"],
"Subjects": [10, 20],
}
)
tasks = io.create_tasks_table(df)
assert "Name" in tasks.columns
assert "Num_Exp" in tasks.columns
assert "all" in tasks["Name"].values
# --- Test check_params ---
def test_check_params_cutoff_enabled():
params = {"cutoff_predict_enabled": True}
out = io.check_params(params)
assert out["significance_threshold"] == 0.05
assert out["cluster_forming_threshold"] == 0.001
assert out["monte_carlo_iterations"] == 5000
def test_check_params_cutoff_disabled():
params = {"cutoff_predict_enabled": False}
out = io.check_params(params)
# Should not ioify parameters
assert "significance_threshold" not in out
# --- Test check_for_exp_independence ---
def test_check_for_exp_independence_warns(caplog):
df = pd.DataFrame(
{
"Articles": ["art1", "art1", "art2"],
"Tags": [("a",), ("b",), ("c",)],
}
)
with caplog.at_level("WARNING"):
io.check_for_exp_independence(df)
assert "multiple experiments" in caplog.text
def test_check_for_exp_independence_no_warn(caplog):
df = pd.DataFrame(
{
"Articles": ["art1", "art2"],
"Tags": [("a",), ("b",)],
}
)
with caplog.at_level("WARNING"):
io.check_for_exp_independence(df)
assert "multiple experiments" not in caplog.text