diff --git a/.gitignore b/.gitignore index 977ba28..88b6009 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ tests/IVIMmodels/unit_tests/*.log junit/* ivim_simulation.bval ivim_simulation.bvec +*.pt # Unit test / coverage reports .tox/ @@ -32,4 +33,5 @@ nosetests.xml coverage.xml *.pyc phantoms/MR_XCAT_qMRI/*.json -phantoms/MR_XCAT_qMRI/*.txt \ No newline at end of file +phantoms/MR_XCAT_qMRI/*.txt +tests/IVIMmodels/unit_tests/models diff --git a/conftest.py b/conftest.py index 3c33bda..e9c19ce 100644 --- a/conftest.py +++ b/conftest.py @@ -37,6 +37,12 @@ def pytest_addoption(parser): type=str, help="Default data file name", ) + parser.addoption( + "--dataFileDL", + default="tests/IVIMmodels/unit_tests/generic_DL.json", + type=str, + help="Default data file name", + ) parser.addoption( "--saveFileName", default="", @@ -179,6 +185,10 @@ def pytest_generate_tests(metafunc): if "bound_input" in metafunc.fixturenames: args = bound_input(metafunc.config.getoption("dataFile"),metafunc.config.getoption("algorithmFile")) metafunc.parametrize("bound_input", args) + if "deep_learning_algorithms" in metafunc.fixturenames: + args = deep_learning_algorithms(metafunc.config.getoption("dataFileDL"),metafunc.config.getoption("algorithmFile")) + metafunc.parametrize("deep_learning_algorithms", args) + def data_list(filename): @@ -210,17 +220,18 @@ def data_ivim_fit_saved(datafile, algorithmFile): first = True for name, data in all_data.items(): algorithm_dict = algorithm_information.get(algorithm, {}) - xfail = {"xfail": name in algorithm_dict.get("xfail_names", {}), - "strict": algorithm_dict.get("xfail_names", {}).get(name, True)} - kwargs = algorithm_dict.get("options", {}) - tolerances = algorithm_dict.get("tolerances", {}) - skiptime=False - if first: - if algorithm_dict.get("fail_first_time", False): - skiptime = True - first = False - requires_matlab = algorithm_dict.get("requires_matlab", False) - yield name, bvals, data, algorithm, xfail, kwargs, tolerances, skiptime, requires_matlab + if not algorithm_dict.get('deep_learning',False): + xfail = {"xfail": name in algorithm_dict.get("xfail_names", {}), + "strict": algorithm_dict.get("xfail_names", {}).get(name, True)} + kwargs = algorithm_dict.get("options", {}) + tolerances = algorithm_dict.get("tolerances", {}) + skiptime=False + if first: + if algorithm_dict.get("fail_first_time", False): + skiptime = True + first = False + requires_matlab = algorithm_dict.get("requires_matlab", False) + yield name, bvals, data, algorithm, xfail, kwargs, tolerances, skiptime, requires_matlab def algorithmlist(algorithmFile): # Find the algorithms from algorithms.json @@ -233,7 +244,7 @@ def algorithmlist(algorithmFile): for algorithm in algorithms: algorithm_dict = algorithm_information.get(algorithm, {}) requires_matlab = algorithm_dict.get("requires_matlab", False) - yield algorithm, requires_matlab + yield algorithm, requires_matlab, algorithm_dict.get('deep_learning', False) def bound_input(datafile,algorithmFile): # Find the algorithms from algorithms.json @@ -251,9 +262,31 @@ def bound_input(datafile,algorithmFile): for name, data in all_data.items(): for algorithm in algorithms: algorithm_dict = algorithm_information.get(algorithm, {}) - xfail = {"xfail": name in algorithm_dict.get("xfail_names", {}), - "strict": algorithm_dict.get("xfail_names", {}).get(name, True)} + if not algorithm_dict.get('deep_learning',False): + xfail = {"xfail": name in algorithm_dict.get("xfail_names", {}), + "strict": algorithm_dict.get("xfail_names", {}).get(name, True)} + kwargs = algorithm_dict.get("options", {}) + tolerances = algorithm_dict.get("tolerances", {}) + requires_matlab = algorithm_dict.get("requires_matlab", False) + yield name, bvals, data, algorithm, xfail, kwargs, tolerances, requires_matlab + +def deep_learning_algorithms(datafile,algorithmFile): + # Find the algorithms from algorithms.json + current_folder = pathlib.Path.cwd() + algorithm_path = current_folder / algorithmFile + with algorithm_path.open() as f: + algorithm_information = json.load(f) + # Load generic test data generated from the included phantom: phantoms/MR_XCAT_qMRI + generic = current_folder / datafile + with generic.open() as f: + all_data = json.load(f) + algorithms = algorithm_information["algorithms"] + bvals = all_data.pop('config') + bvals = bvals['bvalues'] + for algorithm in algorithms: + algorithm_dict = algorithm_information.get(algorithm, {}) + if algorithm_dict.get('deep_learning',False): kwargs = algorithm_dict.get("options", {}) - tolerances = algorithm_dict.get("tolerances", {}) requires_matlab = algorithm_dict.get("requires_matlab", False) - yield name, bvals, data, algorithm, xfail, kwargs, tolerances, requires_matlab \ No newline at end of file + tolerances = algorithm_dict.get("tolerances", {"atol":{"f": 2e-1, "D": 8e-4, "Dp": 8e-2},"rtol":{"f": 0.2, "D": 0.3, "Dp": 0.4}}) + yield algorithm, all_data, bvals, kwargs, requires_matlab, tolerances \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 29742e3..cc94a5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ pandas sphinx sphinx_rtd_theme pytest-json-report +ivimnet \ No newline at end of file diff --git a/src/original/OGC_AUMC_IVIMNET/Example_1_simple_map.py b/src/original/OGC_AUMC_IVIMNET/Example_1_simple_map.py new file mode 100644 index 0000000..5f55818 --- /dev/null +++ b/src/original/OGC_AUMC_IVIMNET/Example_1_simple_map.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +September 2020 by Oliver Gurney-Champion & Misha Kaandorp +oliver.gurney.champion@gmail.com / o.j.gurney-champion@amsterdamumc.nl +https://www.github.com/ochampion + +Code is uploaded as part of our publication in MRM (Kaandorp et al. Improved unsupervised physics-informed deep learning for intravoxel-incoherent motion modeling and evaluation in pancreatic cancer patients. MRM 2021) + +requirements: +numpy +torch +tqdm +matplotlib +scipy +joblib +""" +import IVIMNET.simulations as sim +from hyperparams import hyperparams as hp_example_1 +import IVIMNET.deep as deep +import time +import torch +import IVIMNET.fitting_algorithms as fit + +# Import parameters +arg = hp_example_1() +arg = deep.checkarg(arg) +print(arg.save_name) +for SNR in arg.sim.SNR: + # this simulates the signal + IVIM_signal_noisy, D, f, Dp = sim.sim_signal(SNR, arg.sim.bvalues, sims=arg.sim.sims, Dmin=arg.sim.range[0][0], + Dmax=arg.sim.range[1][0], fmin=arg.sim.range[0][1], + fmax=arg.sim.range[1][1], Dsmin=arg.sim.range[0][2], + Dsmax=arg.sim.range[1][2], rician=arg.sim.rician) + + start_time = time.time() + # train network + net = deep.learn_IVIM(IVIM_signal_noisy, arg.sim.bvalues, arg) + elapsed_time = time.time() - start_time + print('\ntime elapsed for training: {}\n'.format(elapsed_time)) + + # simulate IVIM signal for prediction + [dwi_image_long, Dt_truth, Fp_truth, Dp_truth] = sim.sim_signal_predict(arg, SNR) + + # predict + start_time = time.time() + paramsNN = deep.predict_IVIM(dwi_image_long, arg.sim.bvalues, net, arg) + elapsed_time = time.time() - start_time + print('\ntime elapsed for inference: {}\n'.format(elapsed_time)) + # remove network to save memory + del net + if arg.train_pars.use_cuda: + torch.cuda.empty_cache() + + start_time = time.time() + # all fitting is done in the fit.fit_dats for the other fitting algorithms (lsq, segmented and Baysesian) + paramsf = fit.fit_dats(arg.sim.bvalues, dwi_image_long, arg.fit) + elapsed_time = time.time() - start_time + print('\ntime elapsed for lsqfit: {}\n'.format(elapsed_time)) + print('results for lsqfit') + + # plot values predict and truth + sim.plot_example1(paramsNN, paramsf, Dt_truth, Fp_truth, Dp_truth, arg, SNR) diff --git a/src/original/OGC_AUMC_IVIMNET/Example_2_simulations.py b/src/original/OGC_AUMC_IVIMNET/Example_2_simulations.py new file mode 100644 index 0000000..815e4ba --- /dev/null +++ b/src/original/OGC_AUMC_IVIMNET/Example_2_simulations.py @@ -0,0 +1,48 @@ +""" +September 2020 by Oliver Gurney-Champion & Misha Kaandorp +oliver.gurney.champion@gmail.com / o.j.gurney-champion@amsterdamumc.nl +https://www.github.com/ochampion + +Code is uploaded as part of our publication in MRM (Kaandorp et al. Improved unsupervised physics-informed deep learning for intravoxel-incoherent motion modeling and evaluation in pancreatic cancer patients. MRM 2021) + +requirements: +numpy +torch +tqdm +matplotlib +scipy +joblib +""" + +# import +import numpy as np +import IVIMNET.simulations as sim +import IVIMNET.deep as deep +from hyperparams import hyperparams as hp_example + +# load hyperparameter +arg = hp_example() +arg = deep.checkarg(arg) + +matlsq = np.zeros([len(arg.sim.SNR), 3, 3]) +matNN = np.zeros([len(arg.sim.SNR), 3, 3]) +stability = np.zeros([len(arg.sim.SNR), 3]) +a = 0 + +for SNR in arg.sim.SNR: + print('\n simulation at SNR of {snr}\n'.format(snr=SNR)) + if arg.fit.do_fit: + matlsq[a, :, :], matNN[a, :, :], stability[a, :] = sim.sim(SNR, arg) + print('\nresults from lsq:') + print(matlsq) + else: + matNN[a, :, :], stability[a, :] = sim.sim(SNR, arg) + a = a + 1 + print('\nresults from NN: columns show themean, the RMSE/mean and the Spearman coef [DvDp,Dvf,fvDp] \n' + 'the rows show D, f and D*\n' + 'and the different matixes repressent the different SNR levels {}:'.format(arg.sim.SNR)) + print(matNN) + # if repeat is higher than 1, then print stability (CVNET) + if arg.sim.repeats > 1: + print('\nstability of NN for D, f and D* at different SNR levels:') + print(stability) diff --git a/src/original/OGC_AUMC_IVIMNET/Example_3_volunteer.py b/src/original/OGC_AUMC_IVIMNET/Example_3_volunteer.py new file mode 100644 index 0000000..e2e49ea --- /dev/null +++ b/src/original/OGC_AUMC_IVIMNET/Example_3_volunteer.py @@ -0,0 +1,108 @@ +""" +September 2020 by Oliver Gurney-Champion & Misha Kaandorp +oliver.gurney.champion@gmail.com / o.j.gurney-champion@amsterdamumc.nl +https://www.github.com/ochampion + +Code is uploaded as part of our publication in MRM (Kaandorp et al. Improved unsupervised physics-informed deep learning for intravoxel-incoherent motion modeling and evaluation in pancreatic cancer patients. MRM 2021) + +requirements: +numpy +torch +tqdm +matplotlib +scipy +joblib +""" + +# this loads all patient data and evaluates it all. +import os +import time +import nibabel as nib +import numpy as np +import IVIMNET.deep as deep +import torch +from IVIMNET.fitting_algorithms import fit_dats +from hyperparams import hyperparams as hp + +arg = hp() +arg = deep.checkarg(arg) + +testdata = False + +### folder patient data +folder = 'example_data' + +### load patient data +print('Load patient data \n') +# load and init b-values +text_file = np.genfromtxt('{folder}/bvalues.bval'.format(folder=folder)) +bvalues = np.array(text_file) +selsb = np.array(bvalues) == 0 +# load nifti +data = nib.load('{folder}/data.nii.gz'.format(folder=folder)) +datas = data.get_fdata() +# reshape image for fitting +sx, sy, sz, n_b_values = datas.shape +X_dw = np.reshape(datas, (sx * sy * sz, n_b_values)) + +### select only relevant values, delete background and noise, and normalise data +S0 = np.nanmean(X_dw[:, selsb], axis=1) +S0[S0 != S0] = 0 +S0 = np.squeeze(S0) +valid_id = (S0 > (0.5 * np.median(S0[S0 > 0]))) +datatot = X_dw[valid_id, :] +# normalise data +S0 = np.nanmean(datatot[:, selsb], axis=1).astype(' self.bounds[1][i] : fit_results[0] = self.bounds[1][i]-epsilon + fit_results = self.OGC_algorithm_array(bvalues, signals, self.neg_log_prior, x0=fit_results, fitS0=self.fitS0, bounds=self.bounds) + + results = {} + results["D"] = fit_results[0] + results["f"] = fit_results[1] + results["Dp"] = fit_results[2] + return results \ No newline at end of file diff --git a/src/standardized/OGC_AmsterdamUMC_biexp.py b/src/standardized/OGC_AmsterdamUMC_biexp.py index 8c74f43..7af0b60 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp.py @@ -1,5 +1,5 @@ from src.wrappers.OsipiBase import OsipiBase -from src.original.OGC_AmsterdamUMC.LSQ_fitting import fit_least_squares +from src.original.OGC_AmsterdamUMC.LSQ_fitting import fit_least_squares, fit_least_squares_array import numpy as np class OGC_AmsterdamUMC_biexp(OsipiBase): @@ -45,6 +45,7 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non #super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues, bounds, initial_guess, fitS0) super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess) self.OGC_algorithm = fit_least_squares + self.OGC_algorithm_array = fit_least_squares_array self.fitS0=fitS0 self.initialize(bounds, initial_guess, fitS0) @@ -83,4 +84,25 @@ def ivim_fit(self, signals, bvalues, **kwargs): results["f"] = fit_results[1] results["Dp"] = fit_results[2] + return results + + def ivim_fit_full_volume(self, signals, bvalues, **kwargs): + """Perform the IVIM fit + + Args: + signals (array-like) + bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None. + + Returns: + _type_: _description_ + """ + + bvalues=np.array(bvalues) + fit_results = self.OGC_algorithm_array(bvalues, signals, p0=self.initial_guess, bounds=self.bounds, fitS0=self.fitS0) + + results = {} + results["D"] = fit_results[0] + results["f"] = fit_results[1] + results["Dp"] = fit_results[2] + return results \ No newline at end of file diff --git a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py index 5219581..3e9f809 100644 --- a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py +++ b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py @@ -1,5 +1,5 @@ from src.wrappers.OsipiBase import OsipiBase -from src.original.OGC_AmsterdamUMC.LSQ_fitting import fit_segmented +from src.original.OGC_AmsterdamUMC.LSQ_fitting import fit_segmented, fit_segmented_array import numpy as np class OGC_AmsterdamUMC_biexp_segmented(OsipiBase): @@ -44,6 +44,7 @@ def __init__(self, bvalues=None, thresholds=150, bounds=None, initial_guess=None """ super(OGC_AmsterdamUMC_biexp_segmented, self).__init__(bvalues, thresholds, bounds, initial_guess) self.OGC_algorithm = fit_segmented + self.OGC_algorithm_array = fit_segmented_array self.initialize(bounds, initial_guess, thresholds) @@ -65,6 +66,7 @@ def initialize(self, bounds, initial_guess, thresholds): print('warning, no thresholds were defined, so default bounds are used of 150') else: self.thresholds = thresholds + def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -84,4 +86,27 @@ def ivim_fit(self, signals, bvalues, **kwargs): results["f"] = fit_results[1] results["Dp"] = fit_results[2] + return results + + + def ivim_fit_full_volume(self, signals, bvalues, **kwargs): + """Perform the IVIM fit + + Args: + signals (array-like) + bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None. + + Returns: + _type_: _description_ + """ + signals = self.osipi_reshape_to_voxelwise(signals) + + bvalues=np.array(bvalues) + fit_results = self.OGC_algorithm_array(bvalues, signals, bounds=self.bounds, cutoff=self.thresholds, p0=self.initial_guess) + + results = {} + results["D"] = fit_results[0] + results["f"] = fit_results[1] + results["Dp"] = fit_results[2] + return results \ No newline at end of file diff --git a/src/standardized/Super_IVIM_DC.py b/src/standardized/Super_IVIM_DC.py new file mode 100644 index 0000000..a9da09a --- /dev/null +++ b/src/standardized/Super_IVIM_DC.py @@ -0,0 +1,137 @@ +from src.wrappers.OsipiBase import OsipiBase +import numpy as np +import os +from super_ivim_dc.train import train +from pathlib import Path +from super_ivim_dc.infer import infer_from_signal +import warnings + + +class Super_IVIM_DC(OsipiBase): + """ + Bi-exponential fitting algorithm by Oliver Gurney-Champion, Amsterdam UMC + """ + + # I'm thinking that we define default attributes for each submission like this + # And in __init__, we can call the OsipiBase control functions to check whether + # the user inputs fulfil the requirements + + # Some basic stuff that identifies the algorithm + id_author = "" + id_algorithm_type = "Supervised Deep learnt bi-exponential fit with data consistency" + id_return_parameters = "f, D*, D, S0" + id_units = "seconds per milli metre squared or milliseconds per micro metre squared" + + # Algorithm requirements + required_bvalues = 4 + required_thresholds = [0, + 0] # Interval from "at least" to "at most", in case submissions allow a custom number of thresholds + required_bounds = False + required_bounds_optional = True # Bounds may not be required but are optional + required_initial_guess = False + required_initial_guess_optional = True + accepted_dimensions = 1 # Not sure how to define this for the number of accepted dimensions. Perhaps like the thresholds, at least and at most? + + + # Supported inputs in the standardized class + supported_bounds = True + supported_initial_guess = True + supported_thresholds = False + + def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, fitS0=True, SNR = None): + """ + Everything this algorithm requires should be implemented here. + Number of segmentation thresholds, bounds, etc. + + Our OsipiBase object could contain functions that compare the inputs with + the requirements. + """ + if bvalues == None: + raise ValueError("for deep learning models, bvalues need defining at initiaition") + super(Super_IVIM_DC, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess) + self.fitS0=fitS0 + self.bvalues=np.array(bvalues) + self.initialize(bounds, initial_guess, fitS0, SNR) + + def initialize(self, bounds, initial_guess, fitS0, SNR, working_dir=os.getcwd(),ivimnet_filename='ivimnet',super_ivim_dc_filename='super_ivim_dc'): + if SNR is None: + warnings.warn('No SNR indicated. Data simulated with SNR = 100') + SNR=100 + self.fitS0=fitS0 + self.use_initial_guess = False + self.use_bounds = False + self.deep_learning = True + self.supervised = True + modeldir = Path(working_dir) # Ensure it's a Path object + modeldir = modeldir / "models" + modeldir.mkdir(parents=True, exist_ok=True) + working_dir: str = str(modeldir) + super_ivim_dc_filename: str = super_ivim_dc_filename # do not include .pt + ivimnet_filename: str = ivimnet_filename # do not include .pt + self.super_ivim_dc_filename=super_ivim_dc_filename + self.ivimnet_filename=ivimnet_filename + self.working_dir=working_dir + train( + SNR=SNR, + bvalues=self.bvalues, + super_ivim_dc=True, + work_dir=self.working_dir, + super_ivim_dc_filename=self.super_ivim_dc_filename, + ivimnet_filename=ivimnet_filename, + verbose=False, + ivimnet=False + ) + + + def ivim_fit(self, signals, bvalues, **kwargs): + """Perform the IVIM fit + + Args: + signals (array-like) + bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None. + + Returns: + results: a dictionary containing "d", "f", and "Dp". + """ + if not np.array_equal(bvalues, self.bvalues): + raise ValueError("bvalue list at fitting must be identical as the one at initiation, otherwise it will not run") + + Dp, Dt, f, S0_superivimdc = infer_from_signal( + signal=signals, + bvalues=self.bvalues, + model_path=f"{self.working_dir}/{self.super_ivim_dc_filename}.pt", + ) + + results = {} + results["D"] = Dt + results["f"] = f + results["Dp"] = Dp + + return results + + + def test_deep_learning_algorithms(self, signals, bvalues, **kwargs): + """Perform the IVIM fit + + Args: + signals (array-like) + bvalues (array-like, optional): b-values for the signals. If None, self.bvalues will be used. Default is None. + + Returns: + results: a dictionary containing "d", "f", and "Dp". + """ + if not np.array_equal(bvalues, self.bvalues): + raise ValueError("bvalue list at fitting must be identical as the one at initiation, otherwise it will not run") + + Dp, Dt, f, S0_superivimdc = infer_from_signal( + signal=signals, + bvalues=self.bvalues, + model_path=f"{self.working_dir}/{self.super_ivim_dc_filename}.pt", + ) + + results = {} + results["D"] = Dt + results["f"] = f + results["Dp"] = Dp + + return results diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index 372124a..471c246 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -4,6 +4,8 @@ import pathlib import sys from tqdm import tqdm +from utilities.data_simulation.GenerateData import GenerateData + class OsipiBase: """The base class for OSIPI IVIM fitting""" @@ -16,6 +18,8 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.initial_guess = np.asarray(initial_guess) if initial_guess is not None else None self.use_bounds = True self.use_initial_guess = True + self.deep_learning = False + self.supervised = False # If the user inputs an algorithm to OsipiBase, it is intereprete as initiating # an algorithm object with that name. if algorithm: @@ -111,20 +115,21 @@ def osipi_fit(self, data, bvalues=None, **kwargs): single_voxel_data = data[ijk] single_voxel_data_normalization_factor = np.mean(single_voxel_data[b0_indices]) single_voxel_data_normalized = single_voxel_data/single_voxel_data_normalization_factor - + args = [single_voxel_data_normalized, use_bvalues] fit = self.ivim_fit(*args, **kwargs) # For single voxel fits, we assume this is a dict with a float value per key. for key in list(fit.keys()): results[key][ijk] = fit[key] - + #self.parameter_estimates = self.ivim_fit(data, bvalues) return results - + + def osipi_fit_full_volume(self, data, bvalues=None, **kwargs): """Sends a full volume in one go to the fitting algorithm. The osipi_fit method only sends one voxel at a time. Args: - data (array): 3D (single slice) or 4D (multi slice) DWI data. + data (array): 2D (data x b-values), 3D (single slice) or 4D (multi slice) DWI data, with last dimension the b-value dimension. bvalues (array, optional): The b-values of the DWI data. Defaults to None. Returns: @@ -169,7 +174,8 @@ def osipi_fit_full_volume(self, data, bvalues=None, **kwargs): print("Full volume fitting not supported for this algorithm") return False - + + def osipi_print_requirements(self): """ Prints the requirements of the algorithm. @@ -275,11 +281,11 @@ def osipi_check_required_initial_guess(self): return True - def osipi_check_required_bvalues(): + def osipi_check_required_bvalues(self): """Minimum number of b-values required""" pass - def osipi_author(): + def osipi_author(self): """Author identification""" return '' @@ -312,5 +318,15 @@ def osipi_simple_bias_and_RMSE_test(self, SNR, bvalues, f, Dstar, D, noise_reali print(f"f bias:\t{f_bias}\nf RMSE:\t{f_RMSE}") print(f"Dstar bias:\t{Dstar_bias}\nDstar RMSE:\t{Dstar_RMSE}") print(f"D bias:\t{D_bias}\nD RMSE:\t{D_RMSE}") - - + + def osipi_training_data(self, bvalues, data=None, SNR=(5,1000), n=1000000,Drange=(0.0005,0.0034),frange=(0,1),Dprange=(0.005,0.1),rician_noise=False): + rng = np.random.RandomState(42) + if data is None: + gen = GenerateData(rng=rng) + data, D, f, Dp = gen.simulate_training_data(bvalues, SNR=SNR, n=n,Drange=Drange,frange=frange,Dprange=Dprange,rician_noise=rician_noise) + if self.supervised: + self.train_data = {'data':data,'D':D,'f':f,'Dp':Dp} + else: + self.train_data = {'data': data} + + diff --git a/tests/IVIMmodels/unit_tests/algorithms.json b/tests/IVIMmodels/unit_tests/algorithms.json index fa90ce5..051a259 100644 --- a/tests/IVIMmodels/unit_tests/algorithms.json +++ b/tests/IVIMmodels/unit_tests/algorithms.json @@ -13,8 +13,12 @@ "OGC_AmsterdamUMC_biexp", "PV_MUMC_biexp", "PvH_KB_NKI_IVIMfit", - "OJ_GU_seg" + "OJ_GU_seg", + "IVIM_NEToptim" ], + "IVIM_NEToptim": { + "deep_learning": true + }, "ASD_MemorialSloanKettering_QAMPER_IVIM": { "requires_matlab": true }, diff --git a/tests/IVIMmodels/unit_tests/generic_DL.json b/tests/IVIMmodels/unit_tests/generic_DL.json new file mode 100644 index 0000000..46fd494 --- /dev/null +++ b/tests/IVIMmodels/unit_tests/generic_DL.json @@ -0,0 +1,782 @@ +{ + "Myocardium LV": { + "noise": 0.04, + "D": 0.0024, + "f": 0.15, + "Dp": 0.08, + "data": [ + 1.033180924902301, + 0.9607468657793776, + 0.912606451182656, + 0.888620393895259, + 0.8418819322382458, + 0.884731423360733, + 0.8948278336097781, + 0.8166950158442055, + 0.6786227776445861, + 0.6874715517646282, + 0.6056532504758427, + 0.47986924885264165, + 0.41929421834511005, + 0.37360032607203914, + 0.28445075605440917, + 0.1282091905031671, + 0.07311015923259691, + 0.0856679032303328, + 0.042585284457289964, + 0.0033612521618336458 + ] + }, + "myocardium RV": { + "noise": 0.04, + "D": 0.0024, + "f": 0.15, + "Dp": 0.08, + "data": [ + 1.0145043798675288, + 0.9660196612863166, + 0.8867224861101718, + 0.91705752667823, + 0.8595526518110584, + 0.8017899336155123, + 0.7550511551970039, + 0.8086809241124706, + 0.688194063129757, + 0.6299522963355337, + 0.5469383807534097, + 0.4608083021236803, + 0.41392592502242126, + 0.37413426739765343, + 0.16685997968777203, + 0.20901258338016387, + 0.09652919594429378, + 0.05426096728953708, + 0.06155920372827745, + 0.03188929035836821 + ] + }, + "myocardium ra": { + "noise": 0.04, + "D": 0.0015, + "f": 0.07, + "Dp": 0.07, + "data": [ + 1.0092083343835259, + 0.9661574802430908, + 0.990206450003812, + 0.9706325316312445, + 0.9767925839190637, + 0.9220420032510354, + 0.896704422240442, + 0.8176423934101318, + 0.7947310478273071, + 0.8513319609173063, + 0.717433034543031, + 0.6668879161875433, + 0.5076708740135724, + 0.41907282532720147, + 0.46365437068395976, + 0.3304714439286537, + 0.23296693987075087, + 0.23487193869963477, + 0.15137581352968732, + 0.19376654820017183 + ] + }, + "Blood RV": { + "noise": 0.04, + "D": 0.003, + "f": 1.0, + "Dp": 0.1, + "data": [ + 1.0032938194881618, + 0.9011074959881675, + 0.8247177241346322, + 0.6217936665654985, + 0.36359185175179465, + 0.10916941666421531, + 0.07514868579091256, + -0.007457187020223218, + -0.015334743664598449, + 0.035584748512989074, + -0.06094237055801714, + 0.02686274837179178, + 0.017606840455411082, + -0.06800439631952913, + -0.0021446239806577104, + -0.04652851137385713, + 0.007809266022958561, + 0.004337716720120069, + -0.022322891625288062, + -0.008937096639451064 + ] + }, + "Blood RA": { + "noise": 0.04, + "D": 0.003, + "f": 1.0, + "Dp": 0.1, + "data": [ + 1.0391432255942619, + 0.8937085668773437, + 0.8561831176249705, + 0.5766637377982473, + 0.32798363085925153, + 0.11937274238334296, + 0.006607428437157149, + 0.041804062920864915, + 0.07147779149049599, + 0.03153301989127758, + -0.02089298817635404, + 0.04708131846092277, + 0.02742898143058931, + 0.022164981689256565, + -0.019016312292452823, + -0.02353957465336293, + 0.024688437266257503, + 0.012942951338985542, + 0.044936193924032856, + 0.048828299443163144 + ] + }, + "muscle": { + "noise": 0.04, + "D": 0.00137, + "f": 0.1, + "Dp": 0.0263, + "data": [ + 0.9771421455163913, + 0.9834454308259023, + 0.9452156251065997, + 1.0044139802947745, + 0.915969921803736, + 0.9422614234128238, + 0.966768746315094, + 0.8868649011898158, + 0.801775641750857, + 0.7421732033410118, + 0.7197688278589072, + 0.6313747309126961, + 0.5570672087225664, + 0.5099772094974028, + 0.4049154491880213, + 0.2851134303775019, + 0.23608983440436468, + 0.2236446560863158, + 0.17276532513782425, + 0.1590053575168229 + ] + }, + "Liver": { + "noise": 0.04, + "D": 0.0015, + "f": 0.11, + "Dp": 0.1, + "data": [ + 1.0045581555314624, + 1.0213638998197319, + 0.969398694488538, + 0.9838565535209267, + 0.9501015461451721, + 0.8958005059278971, + 0.8765465132191194, + 0.8328169780131174, + 0.7324377081858197, + 0.7500542360964734, + 0.7233657030730803, + 0.5519045170693782, + 0.49836479783347865, + 0.46666122408420285, + 0.40173482533228394, + 0.29436604798073696, + 0.2611243113260643, + 0.20861827555417325, + 0.1332704687060602, + 0.12490867518647186 + ] + }, + "gall bladder": { + "noise": 0.04, + "D": 0.003, + "f": 0.0, + "Dp": 0.1, + "data": [ + 0.961905251793993, + 1.0256071884552098, + 1.01551745195674, + 0.9938794858782594, + 1.0704685834478262, + 0.9299061213951083, + 0.9685333762989757, + 0.8005153781940202, + 0.8113824877622958, + 0.7218821468473943, + 0.5744428594179848, + 0.435568637604556, + 0.32544090019276095, + 0.311628739283941, + 0.07262320066184397, + 0.14686758017350968, + 0.12094224665750349, + 0.029529047728226676, + -0.024448290153361626, + 0.037131709830116565 + ] + }, + "esophagus": { + "noise": 0.04, + "D": 0.00167, + "f": 0.32, + "Dp": 0.03, + "data": [ + 0.9760184361452577, + 0.9691484902482909, + 0.9373217631823604, + 0.944451896555969, + 0.8973473615256383, + 0.8887773110682924, + 0.8163527823728226, + 0.6614411629941387, + 0.6248484487586474, + 0.5911848582410676, + 0.4651908816661642, + 0.4243955339989611, + 0.35693330038669246, + 0.28455262538581105, + 0.3203165877858471, + 0.2294822657030623, + 0.12832792887457084, + 0.14195952181202723, + 0.06369348051138615, + 0.08844760465205179 + ] + }, + "esophagus cont": { + "noise": 0.04, + "D": 0.00167, + "f": 0.32, + "Dp": 0.03, + "data": [ + 1.0051933554344066, + 1.0697342478284437, + 0.971896954072006, + 0.9319837287175755, + 0.8490273810019706, + 0.8712650692017283, + 0.7521087435913123, + 0.7415381226589388, + 0.6047774733959446, + 0.526329838554919, + 0.5078906678435741, + 0.44357656439876386, + 0.3957938979976538, + 0.40114747398096984, + 0.32340941997698347, + 0.21077158200702664, + 0.1499924420092853, + 0.12030740941456787, + 0.06993893013863893, + 0.07783462166270105 + ] + }, + "st wall": { + "noise": 0.04, + "D": 0.0015, + "f": 0.3, + "Dp": 0.012, + "data": [ + 0.9691720371276711, + 1.010791807855152, + 1.0182473591167254, + 0.9395515722008307, + 0.8637591505339656, + 0.9627990401016886, + 0.8387755161482753, + 0.8086409516733656, + 0.7202532865809711, + 0.7015690569861011, + 0.5721598465279487, + 0.5218244134009936, + 0.4136332070822221, + 0.410205378899944, + 0.33767467069420626, + 0.27019501543651475, + 0.214186274782251, + 0.1173447673001895, + 0.15697038509717565, + 0.11198444327354337 + ] + }, + "Stomach Contents": { + "noise": 0.04, + "D": 0.003, + "f": 0.0, + "Dp": 0.0, + "data": [ + 0.9448674412553242, + 0.9689765590425553, + 0.9206488704851425, + 0.9851156213963261, + 0.9558038218755933, + 0.9563136333158716, + 0.8776899120482471, + 0.8629908179550725, + 0.7623258249950511, + 0.7535435335403728, + 0.6138120944999792, + 0.4858085139718561, + 0.33283528163573484, + 0.2988151067036357, + 0.15034269944385478, + 0.1079516871853958, + 0.05431961744917384, + 0.0644211011428326, + 0.03192853993335577, + 0.030618252798551963 + ] + }, + "pancreas": { + "noise": 0.04, + "D": 0.0013, + "f": 0.15, + "Dp": 0.01, + "data": [ + 0.9943199557738517, + 0.9615513907492641, + 0.962486593496966, + 1.01325958270585, + 0.9640697696093661, + 0.9557220836708255, + 0.9586366184308593, + 0.8641503962017191, + 0.8627793118164445, + 0.7937023818566902, + 0.6588382726900143, + 0.6124017243901765, + 0.4829344002386456, + 0.6002324161049521, + 0.40566062654474405, + 0.29080684705848964, + 0.268738824423758, + 0.24178418995751585, + 0.18847082013521696, + 0.15022167388023694 + ] + }, + "Right kydney cortex": { + "noise": 0.04, + "D": 0.00212, + "f": 0.097, + "Dp": 0.02, + "data": [ + 0.9557731693110969, + 1.0195153523853666, + 0.9559175939441356, + 1.078489079465684, + 1.0017035283696583, + 0.9197625101858864, + 0.925621475639203, + 0.8835968935990496, + 0.7745063782760604, + 0.692196408222722, + 0.6582108325694905, + 0.5190708826023274, + 0.4662971316152858, + 0.3875660263909982, + 0.2665586409332584, + 0.24631812310457812, + 0.09532963541849249, + 0.12692822927033753, + 0.009594589477006352, + 0.10007541016107514 + ] + }, + "right kidney medulla": { + "noise": 0.04, + "D": 0.00209, + "f": 0.158, + "Dp": 0.019, + "data": [ + 0.9951460440407266, + 0.980610942856737, + 0.9703278436097708, + 0.9043328668604725, + 0.9927638450434675, + 0.8696301856072838, + 0.8624367935145194, + 0.7957704921401572, + 0.7350925130953995, + 0.6597137947500391, + 0.5857209281357799, + 0.5566471657772445, + 0.3446015850700227, + 0.3566570801329421, + 0.27583891025126467, + 0.14534586021572554, + 0.20375485775193092, + 0.052582777324161785, + -0.012436215521854588, + 0.07291283248781157 + ] + }, + "Left kidney cortex": { + "noise": 0.04, + "D": 0.00212, + "f": 0.097, + "Dp": 0.02, + "data": [ + 1.0468880603673782, + 0.9674961622032888, + 0.9599740157579568, + 0.9770689584957297, + 0.9241585552105663, + 0.9337523300028886, + 0.9056495620074213, + 0.82675125219055, + 0.8149564274215881, + 0.774462591410098, + 0.7021252471982236, + 0.5346701074143629, + 0.4264156948403627, + 0.4007208726237645, + 0.30326275733357777, + 0.22137074715932523, + 0.17094397791558952, + 0.13659729188286526, + 0.1362509504307399, + 0.06988468053707798 + ] + }, + "left kidney medulla": { + "noise": 0.04, + "D": 0.00209, + "f": 0.158, + "Dp": 0.019, + "data": [ + 1.0557477501005665, + 0.994611768183197, + 0.9457874471608064, + 0.929758219299485, + 0.9201650315844786, + 0.8681102171061115, + 0.8086520005634749, + 0.7614221432243738, + 0.8315828793825132, + 0.7206385123651122, + 0.6451434950945771, + 0.570051396231597, + 0.43309132118527605, + 0.4108570492596552, + 0.21495066474791216, + 0.19645829811124518, + 0.11746783516277465, + 0.12277927107558091, + 0.06324547430353558, + 0.07374110588538273 + ] + }, + "spleen": { + "noise": 0.04, + "D": 0.0013, + "f": 0.2, + "Dp": 0.03, + "data": [ + 1.0325513541701827, + 1.1123544145261721, + 0.9742249256516672, + 0.9396794841599914, + 0.9251401460661807, + 0.8638837943986638, + 0.8068256778835379, + 0.835132449467526, + 0.7283043235625228, + 0.652224455742792, + 0.6494135821853746, + 0.6068089850706042, + 0.5143419653844743, + 0.5132171828991017, + 0.42760445301671146, + 0.38766009006814756, + 0.33068433537997927, + 0.173839586026749, + 0.2405297177510098, + 0.15418956733631253 + ] + }, + "spinal cord": { + "noise": 0.04, + "D": 0.00041, + "f": 0.178, + "Dp": 0.0289, + "data": [ + 1.0280278609282678, + 0.9894124087290663, + 1.0105934605574332, + 0.9181433920272777, + 0.9747867809146716, + 0.9763212578346278, + 0.8585914189168204, + 0.8680279908139578, + 0.8044938720507925, + 0.740773708345681, + 0.7187040142639285, + 0.7318889153712291, + 0.7199020676728957, + 0.63568702117008, + 0.6145873939327691, + 0.5854508514525849, + 0.5844167380941034, + 0.6247653592062735, + 0.49078978257612843, + 0.5725374742644902 + ] + }, + "Bone Marrow": { + "noise": 0.04, + "D": 0.00043, + "f": 0.145, + "Dp": 0.05, + "data": [ + 0.9976254538819194, + 0.9990268713188439, + 1.0116605563575343, + 0.9582964191157695, + 0.9193641998024293, + 0.8648532025081588, + 0.8101589607889702, + 0.7786552382990123, + 0.9138691875128335, + 0.8078864164068891, + 0.7648654018070447, + 0.7393494595075057, + 0.7248846610107041, + 0.6643784607444544, + 0.6814868439159852, + 0.6677550541709638, + 0.6127763537286554, + 0.587949855689514, + 0.5411836787838665, + 0.5300311974922378 + ] + }, + "Artery": { + "noise": 0.04, + "D": 0.003, + "f": 1.0, + "Dp": 0.1, + "data": [ + 1.1093322195143687, + 0.9034538011166169, + 0.8258585334337737, + 0.6575852179919285, + 0.35728023602074027, + 0.17106578531135835, + 0.0057158524204253755, + -0.01945428203089517, + -0.020670386520029723, + -0.02516291712005238, + -0.052776719800235905, + -0.025525700357161146, + 0.049138768634061504, + -0.011282610820603958, + 0.05508085719475665, + -0.006958005065786897, + 0.06884511450887285, + 0.0003326029908234506, + 0.009666954926399655, + -0.03985248818849165 + ] + }, + "Vein": { + "noise": 0.04, + "D": 0.003, + "f": 1.0, + "Dp": 0.1, + "data": [ + 1.0197210159206758, + 0.9429787474665715, + 0.6953925914091087, + 0.6319150393282043, + 0.36706925994203377, + 0.14296690674202184, + 0.025264949094670943, + -0.09765479732471924, + -0.009241275254220563, + 0.002271145421148856, + -0.10330949477256475, + 0.035109981294065694, + -0.09522147392745144, + -0.03351423697786109, + 0.044976233007460335, + 0.0401738186044833, + -0.05929856960370753, + 0.046736905621406255, + 0.028384824367348835, + -0.007617635920438392 + ] + }, + "asc lower intestine": { + "noise": 0.04, + "D": 0.00131, + "f": 0.69, + "Dp": 0.029, + "data": [ + 1.024804058114021, + 1.0589227837960973, + 0.916087733926352, + 0.96431791613657, + 0.9077820781691823, + 0.721783995723083, + 0.581396538074047, + 0.4652823667373273, + 0.36885881276163374, + 0.3200920672492273, + 0.1645539740144957, + 0.2312098254476927, + 0.17979701066390505, + 0.2429248446681493, + 0.17484777791251824, + 0.1636242361481287, + 0.1187759092815024, + 0.1633931944500911, + 0.1328527078387414, + 0.029581661515141952 + ] + }, + "trans lower intestine": { + "noise": 0.04, + "D": 0.00131, + "f": 0.69, + "Dp": 0.029, + "data": [ + 1.0808728132568524, + 0.9631331259634974, + 0.9488714932223482, + 0.9027144254743407, + 0.8426442298088287, + 0.6385876797045293, + 0.5143605001476169, + 0.46151749219469773, + 0.37590016940881227, + 0.2467568553732959, + 0.29886362793279037, + 0.3194887182864452, + 0.2617604463825949, + 0.19441089287023655, + 0.08863402268576173, + 0.08464409815272762, + 0.11252210733295068, + 0.08392180287776525, + 0.05136814942113898, + 0.07094810163308216 + ] + }, + "desc lower intestine": { + "noise": 0.04, + "D": 0.00131, + "f": 0.69, + "Dp": 0.029, + "data": [ + 1.0260112673785273, + 0.9365985562155295, + 1.0014410060659287, + 0.8724826300763201, + 0.8847865036030733, + 0.6225430013002734, + 0.5897280752966981, + 0.49914460539650857, + 0.3662489884540538, + 0.2934711509706957, + 0.22944553489060396, + 0.2647057112963398, + 0.227458540148848, + 0.1340370644164391, + 0.10100233633792886, + 0.12499528059472875, + 0.13236863091257245, + 0.015595194727086287, + 0.027529914833308175, + 0.06638542442384117 + ] + }, + "small intestine": { + "noise": 0.04, + "D": 0.00131, + "f": 0.69, + "Dp": 0.029, + "data": [ + 1.0091138624277274, + 0.957288677234669, + 1.0146719064272864, + 0.8771328232962504, + 0.7950628243201886, + 0.6623986831798672, + 0.5311387641845149, + 0.4655813243195477, + 0.34914652867817, + 0.30529330726656484, + 0.21676765379402968, + 0.26471170667803834, + 0.18910436178744383, + 0.23519776626374791, + 0.1607019040464443, + 0.025010511236504773, + 0.1273079010812619, + 0.0641423305398623, + 0.007043914528604367, + 0.031613830826212684 + ] + }, + "pericardium": { + "noise": 0.04, + "D": 0.003, + "f": 0.07, + "Dp": 0.01, + "data": [ + 1.0254189562543548, + 1.016708077284339, + 1.0086047114941583, + 0.9319300461110076, + 0.9730576750080627, + 0.9113646625255759, + 0.8973387467530622, + 0.8211640702882257, + 0.739133466365069, + 0.7283851038188696, + 0.6200116264309581, + 0.48141910109657626, + 0.30834880138367365, + 0.27999893914815877, + 0.22533955783346057, + 0.1495047069086678, + 0.04415248468137799, + -0.008840250674543366, + 0.07494032381810614, + 0.04132424950352531 + ] + }, + "config": { + "bvalues": [ + 0.0, + 1.0, + 2.0, + 5.0, + 10.0, + 20.0, + 30.0, + 50.0, + 75.0, + 100.0, + 150.0, + 250.0, + 350.0, + 400.0, + 550.0, + 700.0, + 850.0, + 1000.0, + 1100.0, + 1200.0 + ] + } +} \ No newline at end of file diff --git a/tests/IVIMmodels/unit_tests/test_ivim_fit.py b/tests/IVIMmodels/unit_tests/test_ivim_fit.py index 9ed85b7..3dd0964 100644 --- a/tests/IVIMmodels/unit_tests/test_ivim_fit.py +++ b/tests/IVIMmodels/unit_tests/test_ivim_fit.py @@ -71,7 +71,7 @@ def to_list_if_needed(value): assert elapsed_time < max_time, f"Algorithm {name} took {elapsed_time} seconds, which is longer than 2 second to fit per voxel" #less than 0.5 seconds per voxel def test_default_bounds_and_initial_guesses(algorithmlist,eng): - algorithm, requires_matlab = algorithmlist + algorithm, requires_matlab, deep_learning = algorithmlist if requires_matlab: if eng is None: pytest.skip(reason="Running without matlab; if Matlab is available please run pytest --withmatlab") @@ -79,6 +79,8 @@ def test_default_bounds_and_initial_guesses(algorithmlist,eng): kwargs = {'eng': eng} else: kwargs={} + if deep_learning: + pytest.skip(reason="deep learning algorithms do not take initial guesses. Bound testing for deep learning algorithms not yet implemented") fit = OsipiBase(algorithm=algorithm,**kwargs) #assert fit.bounds is not None, f"For {algorithm}, there is no default fit boundary" #assert fit.initial_guess is not None, f"For {algorithm}, there is no default fit initial guess" @@ -147,8 +149,60 @@ def test_bounds(bound_input, eng): assert passDp, f"Fit still passes when initial guess Ds is out of fit bounds; potentially initial guesses not respected for: {name}" ''' +def test_deep_learning_algorithms(deep_learning_algorithms, record_property): + algorithm, data, bvals, kwargs, requires_matlab, tolerances = deep_learning_algorithms + if requires_matlab: + if eng is None: + pytest.skip("Running without matlab; if Matlab is available please run pytest --withmatlab") + else: + kwargs = {**kwargs, 'eng': eng} + + tolerances = tolerances_helper(tolerances, data) + fit = OsipiBase(bvalues=bvals, algorithm=algorithm, **kwargs) + + array_2d = np.array([dat["data"] for _, dat in data.items()]) + start_time = time.time() + fit_result = fit.osipi_fit_full_volume(array_2d, bvals) + elapsed_time = time.time() - start_time + + errors = [] # Collect all assertion errors + + def to_list_if_needed(value): + return value.tolist() if isinstance(value, np.ndarray) else value + + for i, (name, dat) in enumerate(data.items()): + try: + record_property('test_data', { + "name": name, + "algorithm": algorithm, + "f_fit": to_list_if_needed(fit_result['f'][i]), + "Dp_fit": to_list_if_needed(fit_result['Dp'][i]), + "D_fit": to_list_if_needed(fit_result['D'][i]), + "f": to_list_if_needed(dat['f']), + "Dp": to_list_if_needed(dat['Dp']), + "D": to_list_if_needed(dat['D']), + "rtol": tolerances["rtol"], + "atol": tolerances["atol"] + }) + + npt.assert_allclose(fit_result['f'][i], dat['f'], + rtol=tolerances["rtol"]["f"], atol=tolerances["atol"]["f"]) + + if dat['f'] < 0.80: + npt.assert_allclose(fit_result['D'][i], dat['D'], + rtol=tolerances["rtol"]["D"], atol=tolerances["atol"]["D"]) + + if dat['f'] > 0.03: + npt.assert_allclose(fit_result['Dp'][i], dat['Dp'], + rtol=tolerances["rtol"]["Dp"], atol=tolerances["atol"]["Dp"]) + + except AssertionError as e: + errors.append(f"{name + ' ' + algorithm+ ' D=' + str(dat['D']) + ' Dp=' + str(dat['Dp']) + ' f=' + str(dat['f'])}: {e}") + + if errors: + all_errors = "\n".join(errors) + raise AssertionError(f"Some tests failed:\n{all_errors}") - \ No newline at end of file diff --git a/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py b/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py index c6c42ce..51206fa 100644 --- a/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py +++ b/tests/IVIMmodels/unit_tests/test_ivim_synthetic.py @@ -12,9 +12,11 @@ @pytest.mark.slow def test_generated(algorithmlist, ivim_data, SNR, rtol, atol, fit_count, rician_noise, save_file, save_duration_file, use_prior,eng): # assert save_file == "test" - ivim_algorithm, requires_matlab = algorithmlist + ivim_algorithm, requires_matlab, deep_learning = algorithmlist if requires_matlab and eng is None: pytest.skip(reason="Running without matlab; if Matlab is available please run pytest --withmatlab") + if deep_learning: + pytest.skip(reason="Slow drifting in performance not yet implmented for deep learning algorithms") #requieres training a network per b-value set and inferencing all data in 1 go. So not 1 data point per time, but all data in 1 go :). Otherwise network will be trained many many times... rng = np.random.RandomState(42) # random.seed(42) S0 = 1 diff --git a/utilities/data_simulation/GenerateData.py b/utilities/data_simulation/GenerateData.py index f1309e2..99dbe14 100644 --- a/utilities/data_simulation/GenerateData.py +++ b/utilities/data_simulation/GenerateData.py @@ -145,3 +145,30 @@ def multilinear_signal(self, D, F, S0, bvalues, offset=0): signal *= S0 signal += offset return signal + + def simulate_training_data(self, bvalues, SNR = (5,100), n = 1000000, Drange = (0.0005,0.0034), frange = (0,1), Dprange = (0.005,0.1), rician_noise = False): + test = self._rng.uniform(0, 1, (n, 1)) + D = Drange[0] + (test * (Drange[1] - Drange[0])) + test = self._rng.uniform(0, 1, (n, 1)) + f = frange[0] + (test * (frange[1] - frange[0])) + test = self._rng.uniform(0, 1, (n, 1)) + Dp = Dprange[0] + (test * (Dprange[1] - Dprange[0])) + data_sim = np.zeros([len(D), len(bvalues)]) + bvalues = np.array(bvalues) + if type(SNR) == tuple: + test = self._rng.uniform(0, 1, (n, 1)) + SNR = np.exp(np.log(SNR[1]) + (test * (np.log(SNR[0]) - np.log(SNR[1])))) + addnoise = True + elif SNR == 0: + addnoise = False + else: + SNR = SNR * np.ones_like(Dp) + addnoise = True + # loop over array to fill with simulated IVIM data + for aa in range(len(D)): + data_sim[aa, :] = self.ivim_signal(D[aa][0], Dp[aa][0], f[aa][0], 1, bvalues, snr=SNR[aa], rician_noise=rician_noise) + # if SNR is set to zero, don't add noise + S0_noisy = np.mean(data_sim[:, bvalues == 0], axis=1) + data_sim = data_sim / S0_noisy[:, None] + return data_sim, D, f, Dp +