Skip to content

Commit

Permalink
refactor(tests): implement pytest fixtures and parameterization
Browse files Browse the repository at this point in the history
  • Loading branch information
MohamedNasser8 committed Feb 19, 2025
1 parent c703ed6 commit 9ca630a
Showing 1 changed file with 78 additions and 74 deletions.
152 changes: 78 additions & 74 deletions tests/test_tissue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,31 @@

import numpy as np
import osipi
import pytest


def test_tissue_tofts():
"""1.
@pytest.fixture
def time_array():
return np.arange(0, 6 * 60, 1)

Basic operation of the function - test that the peak tissue concentration is less than the peak

@pytest.fixture
def aif(time_array):
return osipi.aif_parker(time_array)


@pytest.fixture
def base_params():
return {"Ktrans": 0.6, "ve": 0.2}


def test_tissue_tofts(time_array, aif, base_params):
"""1. Basic operation of the function - test that the peak tissue concentration is less than the peak
AIF
"""

t = np.linspace(0, 6 * 60, 360)
ca = osipi.aif_parker(t)
ct = osipi.tofts(t, ca, Ktrans=0.6, ve=0.2)
assert np.round(np.max(ct)) < np.round(np.max(ca))
ct = osipi.tofts(time_array, aif, discretization_method="exp", **base_params)
assert np.round(np.max(ct)) < np.round(np.max(aif))

# 2. Basic operation of the function - test with non-uniform spacing of
# time array
Expand All @@ -26,45 +37,42 @@ def test_tissue_tofts():

# 3. The offset option - test that the tissue concentration is shifted
# from the AIF by the specified offset time
t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct = osipi.tofts(t, ca, Ktrans=0.6, ve=0.2, Ta=60.0)
assert (np.min(np.where(ct > 0.0)) - np.min(np.where(ca > 0.0)) - 1) * 1 == 60.0
ct = osipi.tofts(time_array, aif, Ta=60.0, **base_params)
assert (np.min(np.where(ct > 0.0)) - np.min(np.where(aif > 0.0)) - 1) * 1 == 60.0

# 4. Test that the discretization options give almost the same result -
# time step must be very small
t = np.arange(0, 6 * 60, 0.01)
ca = osipi.aif_parker(t)
ct_conv = osipi.tofts(t, ca, Ktrans=0.6, ve=0.2)
ct_exp = osipi.tofts(t, ca, Ktrans=0.6, ve=0.2, discretization_method="exp")
ct_conv = osipi.tofts(t, ca, **base_params)
ct_exp = osipi.tofts(t, ca, discretization_method="exp", **base_params)
assert np.allclose(ct_conv, ct_exp, rtol=1e-4, atol=1e-3)

# 5. Test that the ratio of the area under the ct and ca curves is
# approximately the extracellular volume
t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct_conv = osipi.tofts(t, ca, Ktrans=0.6, ve=0.2)
ct_exp = osipi.tofts(t, ca, Ktrans=0.6, ve=0.2, discretization_method="exp")
assert math.isclose(np.trapz(ct_conv, t) / np.trapz(ca, t), 0.2, abs_tol=1e-1)
assert math.isclose(np.trapz(ct_exp, t) / np.trapz(ca, t), 0.2, abs_tol=1e-1)

ct_conv = osipi.tofts(time_array, aif, **base_params)
ct_exp = osipi.tofts(time_array, aif, discretization_method="exp", **base_params)
assert math.isclose(
np.trapz(ct_conv, time_array) / np.trapz(aif, time_array), 0.2, abs_tol=1e-1
)
assert math.isclose(np.trapz(ct_exp, time_array) / np.trapz(aif, time_array), 0.2, abs_tol=1e-1)

# 6. Test specific use cases
t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct_conv = osipi.tofts(t, ca, Ktrans=0, ve=0.2)
ct_conv = osipi.tofts(time_array, aif, Ktrans=0, ve=0.2)
assert np.count_nonzero(ct_conv) == 0

ct_exp = osipi.tofts(t, ca, Ktrans=0, ve=0.2, discretization_method="exp")
ct_exp = osipi.tofts(time_array, aif, Ktrans=0, ve=0.2, discretization_method="exp")
assert np.count_nonzero(ct_exp) == 0

ct_conv = osipi.tofts(t, ca, Ktrans=0.6, ve=0)
ct_conv = osipi.tofts(time_array, aif, Ktrans=0.6, ve=0)
assert np.count_nonzero(ct_conv) == 0

ct_exp = osipi.tofts(t, ca, Ktrans=0.6, ve=0, discretization_method="exp")
ct_exp = osipi.tofts(time_array, aif, Ktrans=0.6, ve=0, discretization_method="exp")
assert np.count_nonzero(ct_exp) == 0


def test_tissue_extended_tofts():
def test_tissue_extended_tofts(time_array, aif):
# 1. Basic operation of the function - test that the peak tissue
# concentration is less than the peak AIF
t = np.linspace(0, 6 * 60, 360)
Expand All @@ -81,10 +89,8 @@ def test_tissue_extended_tofts():

# 3. The offset option - test that the tissue concentration is shifted
# from the AIF by the specified offset time
t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct = osipi.extended_tofts(t, ca, Ktrans=0.6, ve=0.2, vp=0.3, Ta=60.0)
assert (np.min(np.where(ct > 0.0)) - np.min(np.where(ca > 0.0)) - 1) * 1 == 60.0
ct = osipi.extended_tofts(time_array, aif, Ktrans=0.6, ve=0.2, vp=0.3, Ta=60.0)
assert (np.min(np.where(ct > 0.0)) - np.min(np.where(aif > 0.0)) - 1) * 1 == 60.0

# 4. Test that the discretization options give almost the same result -
# time step must be very small
Expand All @@ -96,67 +102,65 @@ def test_tissue_extended_tofts():

# 5. Test that the ratio of the area under the ct and ca curves is
# approximately the extracellular volume plus the plasma volume

t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct_conv = osipi.extended_tofts(t, ca, Ktrans=0.6, ve=0.2, vp=0.3)
ct_exp = osipi.extended_tofts(t, ca, Ktrans=0.6, ve=0.2, vp=0.3, discretization_method="exp")
ct_conv = osipi.extended_tofts(time_array, aif, Ktrans=0.6, ve=0.2, vp=0.3)
ct_exp = osipi.extended_tofts(
time_array, aif, Ktrans=0.6, ve=0.2, vp=0.3, discretization_method="exp"
)
assert math.isclose(
np.trapz(ct_conv, t) / np.trapz(ca, t),
np.trapz(ct_conv, time_array) / np.trapz(aif, time_array),
0.2 + 0.3,
abs_tol=1e-1,
)
assert math.isclose(np.trapz(ct_exp, t) / np.trapz(ca, t), 0.2 + 0.3, abs_tol=1e-1)
assert math.isclose(
np.trapz(ct_exp, time_array) / np.trapz(aif, time_array), 0.2 + 0.3, abs_tol=1e-1
)

# 6. Test specific use cases
t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct_conv = osipi.extended_tofts(t, ca, Ktrans=0, ve=0.2, vp=0.3)
assert np.allclose(ct_conv, ca * 0.3, rtol=1e-4, atol=1e-3)

ct_exp = osipi.extended_tofts(t, ca, Ktrans=0, ve=0.2, vp=0.3, discretization_method="exp")
assert np.allclose(ct_conv, ca * 0.3, rtol=1e-4, atol=1e-3)
ct_conv = osipi.extended_tofts(time_array, aif, Ktrans=0, ve=0.2, vp=0.3)
assert np.allclose(ct_conv, aif * 0.3, rtol=1e-4, atol=1e-3)

ct_conv = osipi.extended_tofts(t, ca, Ktrans=0.6, ve=0, vp=0.3)
assert np.allclose(ct_conv, ca * 0.3, rtol=1e-4, atol=1e-3)
ct_exp = osipi.extended_tofts(
time_array, aif, Ktrans=0, ve=0.2, vp=0.3, discretization_method="exp"
)
assert np.allclose(ct_conv, aif * 0.3, rtol=1e-4, atol=1e-3)

ct_exp = osipi.extended_tofts(t, ca, Ktrans=0.6, ve=0, vp=0.3, discretization_method="exp")
assert np.allclose(ct_conv, ca * 0.3, rtol=1e-4, atol=1e-3)
ct_conv = osipi.extended_tofts(time_array, aif, Ktrans=0.6, ve=0, vp=0.3)
assert np.allclose(ct_conv, aif * 0.3, rtol=1e-4, atol=1e-3)

ct_exp = osipi.extended_tofts(
time_array, aif, Ktrans=0.6, ve=0, vp=0.3, discretization_method="exp"
)
assert np.allclose(ct_conv, aif * 0.3, rtol=1e-4, atol=1e-3)

def test_tissue_two_compartment_exchange_model():
# 1. Basic operation of the function - test that the peak tissue
# concentration is less than the peak AIF
t = np.linspace(0, 6 * 60, 360)
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, Fp=10, PS=5, ve=0.2, vp=0.3)
assert np.round(np.max(ct)) < np.round(np.max(ca))

# 2. Basic operation of the function - test with non-uniform spacing of
# time array
t = np.geomspace(1, 6 * 60 + 1, num=360) - 1
@pytest.mark.parametrize(
"time_points",
[
(np.linspace(0, 6 * 60, 360), "uniform"),
(np.geomspace(1, 6 * 60 + 1, num=360) - 1, "non-uniform"),
],
)
def test_2cxm_basic_operation(time_points):
"""Test that the peak tissue concentration is less than the peak AIF for different time arrays"""
t, _ = time_points
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, Fp=10, PS=5, ve=0.2, vp=0.3)
assert np.round(np.max(ct)) < np.round(np.max(ca))

# 3. The offset option - test that the tissue concentration is shifted
# from the AIF by the specified offset time
t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, Fp=10, PS=5, ve=0.2, vp=0.3, Ta=60.0)
assert (np.min(np.where(ct > 0.0)) - np.min(np.where(ca > 0.0)) - 1) * 1 == 60.0

# 4. Handle case where VP is 0, it should behave like the tofts model
t = np.arange(0, 6 * 60, 1)
ca = osipi.aif_parker(t)
ct = osipi.two_compartment_exchange_model(t, ca, Fp=10, PS=5, ve=0.2, vp=0)
ct_tofts = osipi.tofts(t, ca, Ktrans=3.93, ve=0.2)
def test_2cxm_time_offset(time_array, aif):
"""Test that the tissue concentration is shifted from the AIF by the specified offset time"""
ct = osipi.two_compartment_exchange_model(time_array, aif, Fp=10, PS=5, ve=0.2, vp=0.3, Ta=60.0)
assert (np.min(np.where(ct > 0.0)) - np.min(np.where(aif > 0.0)) - 1) * 1 == 60.0


def test_2cxm_zero_vp_matches_tofts(time_array, aif):
"""Test that 2CXM with vp=0 behaves like the Tofts model"""
ct = osipi.two_compartment_exchange_model(time_array, aif, Fp=10, PS=5, ve=0.2, vp=0)
ct_tofts = osipi.tofts(time_array, aif, Ktrans=3.93, ve=0.2)
assert np.allclose(ct, ct_tofts, rtol=1e-4, atol=1e-3)


if __name__ == "__main__":
test_tissue_tofts()
test_tissue_extended_tofts()
test_tissue_two_compartment_exchange_model()

print("All tissue concentration model tests passed!!")
pytest.main([__file__])

0 comments on commit 9ca630a

Please sign in to comment.