diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml new file mode 100644 index 0000000..ba1d26f --- /dev/null +++ b/.github/workflows/analysis.yml @@ -0,0 +1,63 @@ +name: Algorithm Analysis + +on: + push: + branches: + - 'main' + - 'analysis/**' + +jobs: + build: + + runs-on: ubuntu-latest + continue-on-error: false + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Set up R + uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + - name: Install R dependencies + uses: r-lib/actions/setup-r-dependencies@v2 + with: + packages: | + any::plyr + any::dplyr + any::tidyverse + any::data.table + any::ggplot2 + - name: Generate fitting data + run: | + pip install pytest + python -m pytest -m slow --saveFileName test_output.csv --SNR 10 30 50 100 200 --fitCount 300 --saveDurationFileName test_duration.csv + - name: Generate figures + run: Rscript --vanilla tests/IVIMmodels/unit_tests/analyze.r test_output.csv test_duration.csv + - name: Upload raw data + uses: actions/upload-artifact@v3 + with: + name: Raw data + path: | + test_output.csv + test_duration.csv + - name: Upload figures + uses: actions/upload-artifact@v3 + with: + name: Fit figures + path: | + D.pdf + f.pdf + Dp.pdf + D_limited.pdf + f_limited.pdf + Dp_limited.pdf + durations.pdf diff --git a/README.md b/README.md index 5cb4185..6f9c976 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,4 @@ The **test** folder contains the test files corresponding to the contributed cod The **utils** folder contains various helpful tools. ## View Testing Reports -*to be added* +[![Unit tests](https://github.com/OSIPI/TF2.4_IVIM-MRI_CodeCollection/actions/workflows/unit_test.yml/badge.svg?branch=main)](https://github.com/OSIPI/TF2.4_IVIM-MRI_CodeCollection/actions/workflows/unit_test.yml) diff --git a/conftest.py b/conftest.py index ce16f10..fd6e0ea 100644 --- a/conftest.py +++ b/conftest.py @@ -19,6 +19,12 @@ def pytest_addoption(parser): type=bool, help="Use Rician noise, non-rician is gaussian", ) + parser.addoption( + "--usePrior", + default=False, + type=bool, + help="Use a prior where accepted", + ) parser.addoption( "--algorithmFile", default="tests/IVIMmodels/unit_tests/algorithms.json", @@ -110,6 +116,10 @@ def fit_count(request): def rician_noise(request): return request.config.getoption("--ricianNoise") +@pytest.fixture(scope="session") +def use_prior(request): + return request.config.getoption("--usePrior") + def pytest_generate_tests(metafunc): if "SNR" in metafunc.fixturenames: diff --git a/tests/IVIMmodels/unit_tests/analyze.r b/tests/IVIMmodels/unit_tests/analyze.r index 51bdbf2..27025a6 100644 --- a/tests/IVIMmodels/unit_tests/analyze.r +++ b/tests/IVIMmodels/unit_tests/analyze.r @@ -25,7 +25,7 @@ plot_ivim <- function(data, fileExtension) { f_plot <- ggplot(data, aes(x=Algorithm)) + geom_boxplot(aes(y=f_fitted)) + geom_boxplot(color="red", aes(y=f)) + facet_grid(SNR ~ Region) + scale_x_discrete(guide = guide_axis(angle = 90)) + ylim(0, 1) + ggtitle("Perfusion fraction grid") + ylab("Perfusion fraction") print(f_plot) ggsave(paste("f", fileExtension, sep=""), plot=f_plot, width = 50, height = 50, units = "cm") - D_plot <- ggplot(data, aes(x=Algorithm)) + geom_boxplot(aes(y=D_fitted)) + geom_boxplot(color="red", aes(y=D)) + facet_grid(SNR ~ Region) + scale_x_discrete(guide = guide_axis(angle = 90)) + ggtitle("Diffusion grid") + ylab("Diffusion") + D_plot <- ggplot(data, aes(x=Algorithm)) + geom_boxplot(aes(y=D_fitted)) + geom_boxplot(color="red", aes(y=D)) + facet_grid(SNR ~ Region) + scale_x_discrete(guide = guide_axis(angle = 90)) + ylim(0, 0.005) + ggtitle("Diffusion grid") + ylab("Diffusion") print(D_plot) ggsave(paste("D", fileExtension, sep=""), plot=D_plot, width = 50, height = 50, units = "cm") Dp_plot <- ggplot(data, aes(x=Algorithm)) + geom_boxplot(aes(y=Dp_fitted)) + geom_boxplot(color="red", aes(y=Dp)) + facet_grid(SNR ~ Region) + scale_x_discrete(guide = guide_axis(angle = 90)) + ylim(0, 0.25) + ggtitle("Perfusion grid") + ylab("Perfusion") @@ -43,8 +43,8 @@ plot_ivim(data_restricted, "_limited.pdf") data_duration <- read.csv(duration_name) data_duration <- data_duration %>% mutate_if(is.character, as.factor) data_duration$ms <- data_duration$Duration..us./data_duration$Count/1000 -ggplot(data_duration, aes(x=Algorithm, y=ms)) + geom_boxplot() + scale_x_discrete(guide = guide_axis(angle = 90)) + ggtitle("Fit Duration") + ylab("Time (ms)") -ggsave("durations.pdf", width = 20, height = 20, units = "cm") +duration_plot <- ggplot(data_duration, aes(x=Algorithm, y=ms)) + geom_boxplot() + scale_x_discrete(guide = guide_axis(angle = 90)) + ggtitle("Fit Duration") + ylab("Time (ms)") +ggsave("durations.pdf", plot=duration_plot, width = 20, height = 20, units = "cm") if (runPrediction) { diff --git a/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py b/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py index 37ca940..cf4b09a 100644 --- a/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py +++ b/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py @@ -14,7 +14,7 @@ #run using pytest --saveFileName test_output.txt --SNR 50 100 200 #e.g. pytest -m slow tests/IVIMmodels/unit_tests/test_ivim_synthetic.py --saveFileName test_output.csv --SNR 10 50 100 200 --fitCount 20 @pytest.mark.slow -def test_generated(ivim_algorithm, ivim_data, SNR, rtol, atol, fit_count, rician_noise, save_file, save_duration_file): +def test_generated(ivim_algorithm, ivim_data, SNR, rtol, atol, fit_count, rician_noise, save_file, save_duration_file, use_prior): # assert save_file == "test" random.seed(42) S0 = 1 @@ -24,8 +24,8 @@ def test_generated(ivim_algorithm, ivim_data, SNR, rtol, atol, fit_count, rician f = data["f"] Dp = data["Dp"] fit = OsipiBase(algorithm=ivim_algorithm) - # here try a prior, but it's not seeing it, why? - if hasattr(fit, "accepts_priors") and fit.accepts_priors: + # here is a prior + if use_prior and hasattr(fit, "accepts_priors") and fit.accepts_priors: prior = [np.random.normal(D, D/3, 10), np.random.normal(f, f/3, 10), np.random.normal(Dp, Dp/3, 10), np.random.normal(1, 1/3, 10)] # prior = [np.repeat(D, 10)+np.random.normal(0,D/3,np.shape(np.repeat(D, 10))), np.repeat(f, 10)+np.random.normal(0,f/3,np.shape(np.repeat(D, 10))), np.repeat(Dp, 10)+np.random.normal(0,Dp/3,np.shape(np.repeat(D, 10))),np.repeat(np.ones_like(Dp), 10)+np.random.normal(0,1/3,np.shape(np.repeat(D, 10)))] # D, f, D* fit.initialize(prior_in=prior)