diff --git a/.github/workflows/PyPi_installer.yml b/.github/workflows/PyPi_installer.yml new file mode 100644 index 00000000..d375b680 --- /dev/null +++ b/.github/workflows/PyPi_installer.yml @@ -0,0 +1,32 @@ +# Publishes to TestPyPI when you push a tag starting with "test-v" +on: + push: + tags: + - "test-v*" + +# Publishes to real PyPI when you create a proper GitHub release + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: pip install build + - run: python -m build + + - name: Publish to TestPyPI + if: startsWith(github.ref, 'refs/tags/test-v') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + - name: Publish to PyPI + if: github.event_name == 'release' + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..b51ab96b --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,56 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +title: "OSIPI TF2.4 IVIM-MRI Code Collection" +version: 0.1.0 +date-released: 2026 +license: Apache-2.0 +repository-code: "https://github.com/OSIPI/TF2.4_IVIM-MRI_CodeCollection" +authors: + - family-names: Jalnefjord + given-names: Oscar + orcid: "https://orcid.org/XXXX-XXXX-XXXX-XXXX" + affiliation: "University of Gothenburg" + - family-names: Rashid + given-names: Ivan A. + affiliation: "Lund University" + - family-names: Kuppens + given-names: Daan + affiliation: "Amsterdam University Medical Center" + - family-names: van der Thiel + given-names: Merel M. + affiliation: "Maastricht University Medical Center" + - family-names: van Houdt + given-names: Petra J. + affiliation: "Netherlands Cancer Institute" + - family-names: Voorter + given-names: Paulien H.M. + affiliation: "Maastricht University Medical Center" + - family-names: Peterson + given-names: Eric T. + affiliation: "SRI International" + - family-names: Gurney-Champion + given-names: Oliver J. + affiliation: "Amsterdam University Medical Center" +preferred-citation: + type: article + title: "An open-source code repository for intravoxel incoherent motion analysis: ISMRM Open Science Initiative for Perfusion Imaging (OSIPI)" + authors: + - family-names: Jalnefjord + given-names: Oscar + - family-names: Rashid + given-names: Ivan A. + - family-names: Kuppens + given-names: Daan + - family-names: van der Thiel + given-names: Merel M. + - family-names: van Houdt + given-names: Petra J. + - family-names: Voorter + given-names: Paulien H.M. + - family-names: Peterson + given-names: Eric T. + - family-names: Gurney-Champion + given-names: Oliver J. + journal: "Magnetic Resonance in Medicine" + year: 2026 + doi: "10.XXXX/XXXXXXX" # fill in once published diff --git a/conftest.py b/conftest.py index 5eba87d8..b0de0ed6 100644 --- a/conftest.py +++ b/conftest.py @@ -282,8 +282,8 @@ def bound_input(datafile, algorithms): for algorithm in algorithms["algorithms"]: algorithm_dict = algorithms.get(algorithm, {}) 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)} + xfail = {"xfail": name in algorithm_dict.get("xfail_names", {}) or "bounds" in algorithm_dict.get("xfail_names", {}), + "strict": algorithm_dict.get("xfail_names", {}).get("bounds", 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) diff --git a/phantoms/MR_XCAT_qMRI/sim_ivim_sig.py b/phantoms/MR_XCAT_qMRI/sim_ivim_sig.py index 6934e014..772744e4 100644 --- a/phantoms/MR_XCAT_qMRI/sim_ivim_sig.py +++ b/phantoms/MR_XCAT_qMRI/sim_ivim_sig.py @@ -451,8 +451,10 @@ def parse_bvalues_file(file_path): voxel_selector_fraction = 0.5 D, f, Ds = contrast_curve_calc() - ignore = np.isnan(D) + ignore = np.logical_or(np.logical_or(np.isnan(D),f<0.03),f>0.97) generic_data = {} + seen_combinations = set() + for level, name in legend.items(): if len(ignore) > level and ignore[level]: continue @@ -460,6 +462,18 @@ def parse_bvalues_file(file_path): voxels = sig[selector] if len(voxels) < 1: continue + D_val = np.median(Dim[selector], axis=0) + f_val = np.median(fim[selector], axis=0) + Dp_val = np.median(Dpim[selector], axis=0) + + combo = (tuple(np.atleast_1d(D_val)), + tuple(np.atleast_1d(f_val)), + tuple(np.atleast_1d(Dp_val))) + + if combo in seen_combinations: + continue + + seen_combinations.add(combo) signals = np.squeeze(voxels[int(voxels.shape[0] * voxel_selector_fraction)]).tolist() generic_data[name] = { 'noise': noise, diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..020f0afe --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "osipi-ivim" +version = "0.1.0" +description = "OSIPI TF2.4 IVIM-MRI Code Collection" +license = {text = "Apache-2.0"} +readme = "README.md" +requires-python = ">=3.11" +authors = [ + {name = "Oscar Jalnefjord", email = "oscar.jalnefjord@gu.se"}, + {name = "Ivan A. Rashid"}, + {name = "Daan Kuppens"}, + {name = "Merel M. van der Thiel"}, + {name = "Petra J. van Houdt"}, + {name = "Paulien H.M. Voorter"}, + {name = "Eric T. Peterson"}, + {name = "Oliver J. Gurney-Champion", email = "o.j.gurney-champion@amsterdamumc.nl"}, +] +dependencies = [ + "numpy", + "scipy", + "nibabel", + "torch", + "torchio", + "joblib", + "dipy", + "cvxpy", + "nlopt", + "tqdm", + "pandas", + "statsmodels", + "ivimnet", + "super-ivim-dc>1.0.0", + "zenodo-get", +] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-json-report", +] +docs = [ + "sphinx", + "sphinx_rtd_theme", +] +plot = [ + "matplotlib", + "scienceplots", +] +all = [ + "osipi-ivim[test,docs,plot]", +] + +[project.urls] +Repository = "https://github.com/OSIPI/TF2.4_IVIM-MRI_CodeCollection" + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/pytest.ini b/pytest.ini index 1d85aaef..b3286ddb 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,6 +3,7 @@ markers = slow: marks tests as slow (deselect with '-m "not slow"') addopts = -m 'not slow' + --ignore-glob=**/wip_*.py testpaths = tests filterwarnings = ignore::Warning diff --git a/requirements.txt b/requirements.txt index 3250d946..d445ba1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,7 @@ sphinx_rtd_theme pytest-json-report statsmodels ivimnet -nlopt \ No newline at end of file +nlopt +nipype +itk +#ivim-mri \ No newline at end of file diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/.gitignore diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/IVIM_standard_bcin.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/README.md diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/avgRepeatBvalVols.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/bvalOrderFix.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/dataPrepDWI.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/demo_QAMPER_IVIM.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/getTestData.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/inpaint_nans.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/ivim_modeling_noise.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/outputHNMPAResults.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/qualityFit.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.log b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.log old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.log rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.log diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/returnFeatures.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/qa_fitting/write_param_txt.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/runQAMPERBatch.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/run_QAMPER_IVIM.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/Dir2Arr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/arr2map2.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/deb.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/flipCheck.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/map2arr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/FAQ.pdf b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/FAQ.pdf old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/FAQ.pdf rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/FAQ.pdf diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/NIfTI_tools.pdf b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/NIfTI_tools.pdf old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/NIfTI_tools.pdf rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/NIfTI_tools.pdf diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/UseANALYZE.pdf b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/UseANALYZE.pdf old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/UseANALYZE.pdf rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/UseANALYZE.pdf diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/affine.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bipolar.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/bresenham_line3d.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/clip_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/collapse_nii_scan2.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/examples.txt diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/expand_nii_scan.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/extra_nii_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/flip_lr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/get_nii_frame.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/license.txt diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_ext.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_nii_img.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch0_nii_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_header_only.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/load_untouch_nii_img.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_ana.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/make_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/mat_into_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/pad_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/reslice_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_file_menu.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_orient_ui.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_select_file.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_xhair.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/rri_zoom_menu.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_ext.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_nii_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch0_nii_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_header_only.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_nii_hdr.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/save_untouch_slice.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/unxform_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/verify_nii_ext.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/view_nii_menu.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nifti_toolbox/xform_nii.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_load.m diff --git a/src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m b/src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m old mode 100755 new mode 100644 similarity index 100% rename from src/original/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m rename to src/original/fitting/ASD_MemorialSloanKettering/MRI-QAMPER_IVIM/utils/nii_save.m diff --git a/src/original/DK_OGC_AmsterdamUMC/__init__.py b/src/original/fitting/ASD_MemorialSloanKettering/__init__.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/__init__.py rename to src/original/fitting/ASD_MemorialSloanKettering/__init__.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/__init__.py b/src/original/fitting/DK_OGC_AmsterdamUMC/__init__.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/__init__.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/__init__.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_loading/__init__.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/__init__.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_loading/__init__.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/__init__.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/__init__.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_loading/__init__.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/__init__.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_loading/__init__.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_loading/load_ivim_subject.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_loading/load_ivim_subject.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_loading/load_ivim_subject.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_loading/load_ivim_subject.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/__init__.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/__init__.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/__init__.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/__init__.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/AverageSignalsOfEqualXvals.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/AverageSignalsOfEqualXvals.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/AverageSignalsOfEqualXvals.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/AverageSignalsOfEqualXvals.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/FlattenImageData.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/FlattenImageData.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/FlattenImageData.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/FlattenImageData.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeMaxSignal.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeMaxSignal.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeMaxSignal.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeMaxSignal.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeSignals.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeSignals.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeSignals.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeSignals.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeXvals.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeXvals.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeXvals.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/NormalizeXvals.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalCuration.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalCuration.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalCuration.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalCuration.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalMask.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalMask.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalMask.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SignalMask.py diff --git a/src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SortSignalOnXval.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SortSignalOnXval.py similarity index 100% rename from src/original/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SortSignalOnXval.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/SortSignalOnXval.py diff --git a/src/original/ETP_SRI/__init__.py b/src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/__init__.py similarity index 100% rename from src/original/ETP_SRI/__init__.py rename to src/original/fitting/DK_OGC_AmsterdamUMC/utils/data_processing/processors/__init__.py diff --git a/src/original/fitting/DT_IIITN/__init__.py b/src/original/fitting/DT_IIITN/__init__.py new file mode 100644 index 00000000..70146a93 --- /dev/null +++ b/src/original/fitting/DT_IIITN/__init__.py @@ -0,0 +1 @@ +# WLS IVIM fitting by Devguru Tiwari, IIIT Nagpur diff --git a/src/original/fitting/DT_IIITN/wls_ivim_fitting.py b/src/original/fitting/DT_IIITN/wls_ivim_fitting.py new file mode 100644 index 00000000..d30e88ba --- /dev/null +++ b/src/original/fitting/DT_IIITN/wls_ivim_fitting.py @@ -0,0 +1,170 @@ +""" +Weighted Least Squares (WLS) / Robust Linear Model (RLM) IVIM fitting. + +Author: Devguru Tiwari, IIIT Nagpur +Date: 2026-03-01 + +Implements a segmented approach for IVIM parameter estimation: +1. Estimate D from high b-values using weighted/robust linear regression on log-signal +2. Estimate f from the intercept of the Step 1 fit +3. Estimate D* from residuals at low b-values using weighted/robust linear regression + +Two regression methods are available: +- WLS: Weighted Linear Least Squares with Veraart weights (w = S^2) +- RLM: Robust Linear Model using Huber's T norm (statsmodels) + +Reference: + Veraart, J. et al. (2013). "Weighted linear least squares estimation of + diffusion MRI parameters: strengths, limitations, and pitfalls." + NeuroImage, 81, 335-346. + DOI: 10.1016/j.neuroimage.2013.05.028 + +Requirements: + numpy + statsmodels (only for method="RLM") +""" + +import numpy as np +import warnings + + +def _weighted_linreg(x, y, weights): + """Fast weighted linear regression: y = a + b*x. + + Uses Veraart et al. (2013) approach with weights = S^2. + + Args: + x: 1D array, independent variable. + y: 1D array, dependent variable. + weights: 1D array, weights for each observation. + + Returns: + (intercept, slope) tuple. + """ + W = np.diag(weights) + X = np.column_stack([np.ones_like(x), x]) + # Weighted normal equations: (X^T W X) beta = X^T W y + XtW = X.T @ W + beta = np.linalg.solve(XtW @ X, XtW @ y) + return beta[0], beta[1] # intercept, slope + + +def _rlm_linreg(x, y): + """Robust linear regression using statsmodels RLM with Huber's T norm. + + RLM down-weights outlier observations via iteratively reweighted least + squares (IRLS), making the fit resistant to corrupted/noisy voxels. + + Args: + x: 1D array, independent variable. + y: 1D array, dependent variable. + + Returns: + (intercept, slope) tuple. + """ + import statsmodels.api as sm + X = sm.add_constant(x) + model = sm.RLM(y, X, M=sm.robust.norms.HuberT()) + result = model.fit() + return result.params[0], result.params[1] # intercept, slope + + +def wls_ivim_fit(bvalues, signal, cutoff=200, method="WLS"): + """ + IVIM fit using WLS or RLM (segmented approach). + + Step 1: Fit D from high b-values on log-signal. + Step 2: Fit D* from residuals at low b-values. + + Args: + bvalues (array-like): 1D array of b-values (s/mm²). + signal (array-like): 1D array of signal intensities (will be normalized). + cutoff (float): b-value threshold separating D from D* fitting. + Default: 200 s/mm². + method (str): Regression method to use. + - "WLS": Weighted Least Squares with Veraart S² weights (default). + - "RLM": Robust Linear Model with Huber's T norm (statsmodels). + + Returns: + tuple: (D, f, Dp) where + D (float): True diffusion coefficient (mm²/s). + f (float): Perfusion fraction (0-1). + Dp (float): Pseudo-diffusion coefficient (mm²/s). + """ + method = method.upper() + if method not in ("WLS", "RLM"): + raise ValueError(f"Unknown method '{method}'. Use 'WLS' or 'RLM'.") + + bvalues = np.array(bvalues, dtype=float) + signal = np.array(signal, dtype=float) + + # Normalize signal to S(b=0) + s0_vals = signal[bvalues == 0] + if len(s0_vals) == 0 or np.mean(s0_vals) <= 0: + return 0.0, 0.0, 0.0 + s0 = np.mean(s0_vals) + signal = signal / s0 + + try: + # ── Step 1: Estimate D from high b-values ───────────────────── + # At high b, perfusion component ≈ 0, so: + # S(b) ≈ (1 - f) * exp(-b * D) + # ln(S(b)) = ln(1 - f) - b * D + high_mask = bvalues >= cutoff + b_high = bvalues[high_mask] + s_high = signal[high_mask] + + # Guard against zero/negative signal values + s_high = np.maximum(s_high, 1e-8) + log_s = np.log(s_high) + + if method == "WLS": + # Veraart weights: w = S^2 (corrects for noise in log-domain) + weights_high = s_high ** 2 + intercept, D = _weighted_linreg(-b_high, log_s, weights_high) + else: + # RLM: robust regression, no explicit weights needed + intercept, D = _rlm_linreg(-b_high, log_s) + + # Extract f from intercept: intercept = ln(1 - f) + f = 1.0 - np.exp(intercept) + + # Clamp to physically meaningful ranges + D = np.clip(D, 0, 0.005) + f = np.clip(f, 0, 1) + + # ── Step 2: Estimate D* from low b-value residuals ──────────── + # Subtract the diffusion component: + # residual(b) = S(b) - (1 - f) * exp(-b * D) + # ≈ f * exp(-b * D*) + # ln(residual) = ln(f) - b * D* + residual = signal - (1 - f) * np.exp(-bvalues * D) + + low_mask = (bvalues < cutoff) & (bvalues > 0) + b_low = bvalues[low_mask] + r_low = residual[low_mask] + + # Guard against zero/negative residuals + r_low = np.maximum(r_low, 1e-8) + log_r = np.log(r_low) + + if len(b_low) >= 2: + if method == "WLS": + weights_low = r_low ** 2 + _, Dp = _weighted_linreg(-b_low, log_r, weights_low) + else: + _, Dp = _rlm_linreg(-b_low, log_r) + Dp = np.clip(Dp, 0.005, 0.2) + else: + Dp = 0.01 # fallback + + # Ensure D* > D (by convention) + if Dp < D: + D, Dp = Dp, D + f = 1 - f + + return D, f, Dp + + except Exception: + # If fit fails, return zeros (consistent with other algorithms) + return 0.0, 0.0, 0.0 diff --git a/src/original/ETP_SRI/LinearFitting.py b/src/original/fitting/ETP_SRI/LinearFitting.py similarity index 100% rename from src/original/ETP_SRI/LinearFitting.py rename to src/original/fitting/ETP_SRI/LinearFitting.py diff --git a/src/original/fitting/ETP_SRI/__init__.py b/src/original/fitting/ETP_SRI/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/fitting/IAR_LundUniversity/__init__.py b/src/original/fitting/IAR_LundUniversity/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_biexp.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_biexp.py similarity index 100% rename from src/original/IAR_LundUniversity/ivim_fit_method_biexp.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_biexp.py diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_linear.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_linear.py similarity index 100% rename from src/original/IAR_LundUniversity/ivim_fit_method_linear.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_linear.py diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_modified_mix.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_modified_mix.py similarity index 97% rename from src/original/IAR_LundUniversity/ivim_fit_method_modified_mix.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_modified_mix.py index 1dde9a08..2a383c9c 100644 --- a/src/original/IAR_LundUniversity/ivim_fit_method_modified_mix.py +++ b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_modified_mix.py @@ -86,9 +86,9 @@ def __init__(self, gtab, bounds=None, maxiter=10, xtol=1e-8, rescale_units=False (bounds[0][1]*1000, bounds[1][1]*1000), \ (bounds[0][2]*1000, bounds[1][2]*1000)]) else: # Finally, if units if µm2/ms are already used - self.bounds = np.array([(bounds[0][0], bounds[1][0], \ + self.bounds = np.array([(bounds[0][0], bounds[1][0]), \ (bounds[0][1], bounds[1][1]), \ - (bounds[0][2], bounds[1][2]))]) + (bounds[0][2], bounds[1][2])]) @multi_voxel_fit def fit(self, data, bounds_de=None): diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_modified_topopro.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_modified_topopro.py similarity index 96% rename from src/original/IAR_LundUniversity/ivim_fit_method_modified_topopro.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_modified_topopro.py index 7e0816d1..460227e3 100644 --- a/src/original/IAR_LundUniversity/ivim_fit_method_modified_topopro.py +++ b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_modified_topopro.py @@ -84,9 +84,9 @@ def __init__(self, gtab, bounds=[[0, 0.005, 1e-5], [1, 0.1, 0.004]], \ (bounds[0][1]*1000, bounds[1][1]*1000), \ (bounds[0][2]*1000, bounds[1][2]*1000)]) else: # Finally, if units if µm2/ms are already used - self.bounds = np.array([(bounds[0][0], bounds[1][0], \ + self.bounds = np.array([(bounds[0][0], bounds[1][0]), \ (bounds[0][1], bounds[1][1]), \ - (bounds[0][2], bounds[1][2]))]) + (bounds[0][2], bounds[1][2])]) @multi_voxel_fit def fit(self, data): diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_segmented_2step.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_segmented_2step.py similarity index 96% rename from src/original/IAR_LundUniversity/ivim_fit_method_segmented_2step.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_segmented_2step.py index eebe1265..18ef2628 100644 --- a/src/original/IAR_LundUniversity/ivim_fit_method_segmented_2step.py +++ b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_segmented_2step.py @@ -50,7 +50,7 @@ def fit(self, data): data = data / data_max ### Fit the diffusion signal to bvals >= diff_b_threshold_lower - diff_bounds = [(self.bounds[0][0], self.bounds[0][3]), \ + diff_bounds = [(0, self.bounds[0][3]), \ (self.bounds[1][0], self.bounds[1][3])] # Bounds for S0 and D diff_bval_indices = np.where(self.bvals >= self.diff_b_threshold_lower)[0] diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_segmented_3step.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_segmented_3step.py similarity index 94% rename from src/original/IAR_LundUniversity/ivim_fit_method_segmented_3step.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_segmented_3step.py index 3a2f675e..5e5acda9 100644 --- a/src/original/IAR_LundUniversity/ivim_fit_method_segmented_3step.py +++ b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_segmented_3step.py @@ -54,7 +54,7 @@ def fit(self, data): data = data / data_max ### Fit the diffusion signal to bvals >= diff_b_threshold_lower - diff_bounds = [(self.bounds[0][0], self.bounds[0][3]), \ + diff_bounds = [(0, self.bounds[0][3]), \ (self.bounds[1][0], self.bounds[1][3])] # Bounds for S0 and D diff_bval_indices = np.where(self.bvals >= self.diff_b_threshold_lower)[0] @@ -80,7 +80,8 @@ def fit(self, data): f_est = S0_perf_est/(S0_perf_est + S0_diff_est) # Fit to the full bi-exponential, f estimate as initial guess, D fixed - full_initial_guess = np.array([self.initial_guess[0], f_est, self.initial_guess[2]]) + f_intial_guess = np.min((f_est, self.bounds[0][1])) if f_est > self.bounds[0][1] else np.max((f_est, self.bounds[1][1])) + full_initial_guess = np.array([self.initial_guess[0], f_intial_guess, self.initial_guess[2]]) full_bounds_lower = self.bounds[0][:-1] full_bounds_upper = self.bounds[1][:-1] diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_sivim.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_sivim.py similarity index 100% rename from src/original/IAR_LundUniversity/ivim_fit_method_sivim.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_sivim.py diff --git a/src/original/IAR_LundUniversity/ivim_fit_method_subtracted.py b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_subtracted.py similarity index 95% rename from src/original/IAR_LundUniversity/ivim_fit_method_subtracted.py rename to src/original/fitting/IAR_LundUniversity/ivim_fit_method_subtracted.py index 0780a97a..8f07c910 100644 --- a/src/original/IAR_LundUniversity/ivim_fit_method_subtracted.py +++ b/src/original/fitting/IAR_LundUniversity/ivim_fit_method_subtracted.py @@ -52,7 +52,7 @@ def fit(self, data): data = data / data_max ### Fit the diffusion signal to bvals >= diff_b_threshold_lower - diff_bounds = [(self.bounds[0][0], self.bounds[0][3]), \ + diff_bounds = [(0, self.bounds[0][3]), \ (self.bounds[1][0], self.bounds[1][3])] # Bounds for S0 and D diff_bval_indices = np.where(self.bvals >= self.diff_b_threshold_lower)[0] @@ -64,7 +64,7 @@ def fit(self, data): ### Fit the perfusion signal to bvals <= perf_b_threshold_upper - perf_bounds = [(self.bounds[0][0], self.bounds[0][2]), \ + perf_bounds = [(0, self.bounds[0][2]), \ (self.bounds[1][0], self.bounds[1][2])] # Bounds for S0 and D* perf_bvals = self.bvals[self.bvals <= self.perf_b_threshold_upper] diff --git a/src/original/OGC_AUMC_IVIMNET/Example_1_simple_map.py b/src/original/fitting/OGC_AUMC_IVIMNET/Example_1_simple_map.py similarity index 100% rename from src/original/OGC_AUMC_IVIMNET/Example_1_simple_map.py rename to src/original/fitting/OGC_AUMC_IVIMNET/Example_1_simple_map.py diff --git a/src/original/OGC_AUMC_IVIMNET/Example_2_simulations.py b/src/original/fitting/OGC_AUMC_IVIMNET/Example_2_simulations.py similarity index 100% rename from src/original/OGC_AUMC_IVIMNET/Example_2_simulations.py rename to src/original/fitting/OGC_AUMC_IVIMNET/Example_2_simulations.py diff --git a/src/original/OGC_AUMC_IVIMNET/Example_3_volunteer.py b/src/original/fitting/OGC_AUMC_IVIMNET/Example_3_volunteer.py similarity index 100% rename from src/original/OGC_AUMC_IVIMNET/Example_3_volunteer.py rename to src/original/fitting/OGC_AUMC_IVIMNET/Example_3_volunteer.py diff --git a/src/original/fitting/OGC_AUMC_IVIMNET/__init__.py b/src/original/fitting/OGC_AUMC_IVIMNET/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/OGC_AUMC_IVIMNET/hyperparams.py b/src/original/fitting/OGC_AUMC_IVIMNET/hyperparams.py similarity index 100% rename from src/original/OGC_AUMC_IVIMNET/hyperparams.py rename to src/original/fitting/OGC_AUMC_IVIMNET/hyperparams.py diff --git a/src/original/OGC_AmsterdamUMC/LSQ_fitting.py b/src/original/fitting/OGC_AmsterdamUMC/LSQ_fitting.py similarity index 100% rename from src/original/OGC_AmsterdamUMC/LSQ_fitting.py rename to src/original/fitting/OGC_AmsterdamUMC/LSQ_fitting.py diff --git a/src/original/fitting/OGC_AmsterdamUMC/__init__.py b/src/original/fitting/OGC_AmsterdamUMC/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/OJ_GU/IVIM_bayes.m b/src/original/fitting/OJ_GU/IVIM_bayes.m similarity index 100% rename from src/original/OJ_GU/IVIM_bayes.m rename to src/original/fitting/OJ_GU/IVIM_bayes.m diff --git a/src/original/OJ_GU/IVIM_seg.m b/src/original/fitting/OJ_GU/IVIM_seg.m similarity index 100% rename from src/original/OJ_GU/IVIM_seg.m rename to src/original/fitting/OJ_GU/IVIM_seg.m diff --git a/src/original/fitting/OJ_GU/__init__.py b/src/original/fitting/OJ_GU/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/OJ_GU/info.json b/src/original/fitting/OJ_GU/info.json similarity index 100% rename from src/original/OJ_GU/info.json rename to src/original/fitting/OJ_GU/info.json diff --git a/src/original/OJ_GU/ivim_seg.py b/src/original/fitting/OJ_GU/ivim_seg.py similarity index 100% rename from src/original/OJ_GU/ivim_seg.py rename to src/original/fitting/OJ_GU/ivim_seg.py diff --git a/src/original/fitting/PV_MUMC/__init__.py b/src/original/fitting/PV_MUMC/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/PV_MUMC/triexp_fitting_algorithms.py b/src/original/fitting/PV_MUMC/triexp_fitting_algorithms.py similarity index 100% rename from src/original/PV_MUMC/triexp_fitting_algorithms.py rename to src/original/fitting/PV_MUMC/triexp_fitting_algorithms.py diff --git a/src/original/PV_MUMC/two_step_IVIM_fit.py b/src/original/fitting/PV_MUMC/two_step_IVIM_fit.py similarity index 100% rename from src/original/PV_MUMC/two_step_IVIM_fit.py rename to src/original/fitting/PV_MUMC/two_step_IVIM_fit.py diff --git a/src/original/PvH_KB_NKI/DWI_Examples.py b/src/original/fitting/PvH_KB_NKI/DWI_Examples.py similarity index 100% rename from src/original/PvH_KB_NKI/DWI_Examples.py rename to src/original/fitting/PvH_KB_NKI/DWI_Examples.py diff --git a/src/original/PvH_KB_NKI/DWI_functions_standalone.py b/src/original/fitting/PvH_KB_NKI/DWI_functions_standalone.py similarity index 100% rename from src/original/PvH_KB_NKI/DWI_functions_standalone.py rename to src/original/fitting/PvH_KB_NKI/DWI_functions_standalone.py diff --git a/src/original/fitting/PvH_KB_NKI/__init__.py b/src/original/fitting/PvH_KB_NKI/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/TCML_TechnionIIT/SUPER-IVIM-DC/README.md b/src/original/fitting/TCML_TechnionIIT/SUPER-IVIM-DC/README.md similarity index 100% rename from src/original/TCML_TechnionIIT/SUPER-IVIM-DC/README.md rename to src/original/fitting/TCML_TechnionIIT/SUPER-IVIM-DC/README.md diff --git a/src/original/TCML_TechnionIIT/SUPER-IVIM-DC/sample.ipynb b/src/original/fitting/TCML_TechnionIIT/SUPER-IVIM-DC/sample.ipynb similarity index 100% rename from src/original/TCML_TechnionIIT/SUPER-IVIM-DC/sample.ipynb rename to src/original/fitting/TCML_TechnionIIT/SUPER-IVIM-DC/sample.ipynb diff --git a/src/original/fitting/TCML_TechnionIIT/__init__.py b/src/original/fitting/TCML_TechnionIIT/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/fitting/TF_reference/__init__.py b/src/original/fitting/TF_reference/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/TF_reference/segmented_IVIMfit.py b/src/original/fitting/TF_reference/segmented_IVIMfit.py similarity index 100% rename from src/original/TF_reference/segmented_IVIMfit.py rename to src/original/fitting/TF_reference/segmented_IVIMfit.py diff --git a/src/original/fitting/__init__.py b/src/original/fitting/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/preprocessing/EP_GU/__init__.py b/src/original/preprocessing/EP_GU/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/EP_GU/brain_pipeline.py b/src/original/preprocessing/EP_GU/brain_pipeline.py similarity index 100% rename from src/original/EP_GU/brain_pipeline.py rename to src/original/preprocessing/EP_GU/brain_pipeline.py diff --git a/src/original/EP_GU/info.json b/src/original/preprocessing/EP_GU/info.json similarity index 100% rename from src/original/EP_GU/info.json rename to src/original/preprocessing/EP_GU/info.json diff --git a/src/original/EP_GU/outlier_removal.py b/src/original/preprocessing/EP_GU/outlier_removal.py similarity index 100% rename from src/original/EP_GU/outlier_removal.py rename to src/original/preprocessing/EP_GU/outlier_removal.py diff --git a/src/original/MG_LU/TOPED_fsl_commands.py b/src/original/preprocessing/MG_LU/TOPED_fsl_commands.py similarity index 100% rename from src/original/MG_LU/TOPED_fsl_commands.py rename to src/original/preprocessing/MG_LU/TOPED_fsl_commands.py diff --git a/src/original/MG_LU/TOPED_fsl_runner.py b/src/original/preprocessing/MG_LU/TOPED_fsl_runner.py similarity index 100% rename from src/original/MG_LU/TOPED_fsl_runner.py rename to src/original/preprocessing/MG_LU/TOPED_fsl_runner.py diff --git a/src/original/preprocessing/MG_LU/__init__.py b/src/original/preprocessing/MG_LU/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/original/preprocessing/__init__.py b/src/original/preprocessing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/standardized wip/IAR_LU_linear.py b/src/standardized wip/IAR_LU_linear.py index 7ff310d0..928024b8 100644 --- a/src/standardized wip/IAR_LU_linear.py +++ b/src/standardized wip/IAR_LU_linear.py @@ -1,7 +1,7 @@ import numpy as np from dipy.core.gradients import gradient_table from src.wrappers.OsipiBase import OsipiBase -from src.original.IAR_LundUniversity.ivim_fit_method_linear import IvimModelLinear +from src.original.fitting.IAR_LundUniversity.ivim_fit_method_linear import IvimModelLinear class IAR_LU_linear(OsipiBase): diff --git a/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py b/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py index 3b9ae35d..caf033d2 100644 --- a/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py +++ b/src/standardized/ASD_MemorialSloanKettering_QAMPER_IVIM.py @@ -41,9 +41,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non Our OsipiBase object could contain functions that compare the inputs with the requirements. """ - #super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues, bounds, initial_guess, fitS0) super(ASD_MemorialSloanKettering_QAMPER_IVIM, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess) - self.initialize(bounds, initial_guess) + + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_initial_guess = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + if eng is None: print('initiating matlab; this may take some time. For repeated testing one could use the optional input eng as an already initiated matlab engine') self.eng=matlab.engine.start_matlab() @@ -63,21 +65,6 @@ def algorithm(self,dwi_arr, bval_arr, LB0, UB0, x0in): (f_arr, D_arr, Dx_arr, s0_arr, fitted_dwi_arr, RSS, rms_val, chi, AIC, BIC, R_sq) = results return D_arr/1000, f_arr, Dx_arr/1000, s0_arr - def initialize(self, bounds, initial_guess): - if bounds is None: - print('warning, no bounds were defined, so algorithm-specific default bounds are used') - self.bounds=([1e-6, 0, 0.004, 0],[0.003, 1.0, 0.2, 5]) - else: - self.bounds=bounds - if initial_guess is None: - print('warning, no initial guesses were defined, so algorithm-specific default initial guess is used') - self.initial_guess = [0.001, 0.2, 0.01, 1] - else: - self.initial_guess = initial_guess - self.use_initial_guess = True - self.use_initial_guess = True - self.use_bounds = True - def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -88,12 +75,16 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + + initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] bvalues=np.array(bvalues) - LB = np.array(self.bounds[0])[[1,0,2,3]] - UB = np.array(self.bounds[1])[[1,0,2,3]] + LB = np.array(bounds[0])[[1,0,2,3]] + UB = np.array(bounds[1])[[1,0,2,3]] - fit_results = self.algorithm(np.array(signals)[:,np.newaxis], bvalues, LB, UB, np.array(self.initial_guess)[[1,0,2,3]]) + fit_results = self.algorithm(np.array(signals)[:,np.newaxis], bvalues, LB, UB, np.array(initial_guess)[[1,0,2,3]]) results = {} results["D"] = fit_results[0] diff --git a/src/standardized/DT_IIITN_WLS.py b/src/standardized/DT_IIITN_WLS.py new file mode 100644 index 00000000..97f1fb9a --- /dev/null +++ b/src/standardized/DT_IIITN_WLS.py @@ -0,0 +1,94 @@ +from src.wrappers.OsipiBase import OsipiBase +from src.original.fitting.DT_IIITN.wls_ivim_fitting import wls_ivim_fit +import numpy as np + + +class DT_IIITN_WLS(OsipiBase): + """ + Segmented IVIM fitting with selectable regression method. + + Two methods are available: + - WLS: Weighted Least Squares with Veraart S² weights (default) + - RLM: Robust Linear Model with Huber's T norm (statsmodels) + + Segmented approach: + 1. Estimate D from high b-values using linear regression on log-signal + 2. Estimate D* from residuals at low b-values using linear regression + + Author: Devguru Tiwari, IIIT Nagpur + + Reference: + Veraart, J. et al. (2013). "Weighted linear least squares estimation of + diffusion MRI parameters: strengths, limitations, and pitfalls." + NeuroImage, 81, 335-346. + DOI: 10.1016/j.neuroimage.2013.05.028 + """ + + # Algorithm identification + id_author = "Devguru Tiwari, IIIT Nagpur" + id_algorithm_type = "Weighted least squares / robust linear model segmented fit" + id_return_parameters = "f, D*, D" + id_units = "seconds per milli metre squared or milliseconds per micro metre squared" + id_ref = "https://doi.org/10.1016/j.neuroimage.2013.05.028" + + # Algorithm requirements + required_bvalues = 4 + required_thresholds = [0, 0] + required_bounds = False + required_bounds_optional = False + required_initial_guess = False + required_initial_guess_optional = False + + # Supported inputs + supported_bounds = False + supported_initial_guess = False + supported_thresholds = True + supported_dimensions = 1 + supported_priors = False + + def __init__(self, bvalues=None, thresholds=None, + bounds=None, initial_guess=None, method="WLS"): + """ + Initialize the IVIM fitting algorithm. + + Args: + bvalues (array-like, optional): b-values for the fitted signals. + thresholds (array-like, optional): Threshold b-value for segmented + fitting. The first value is used as the cutoff between high + and low b-values. Default: 200 s/mm². + bounds (dict, optional): Not used by this algorithm. + initial_guess (dict, optional): Not used by this algorithm. + method (str): Regression method — "WLS" (default) or "RLM". + """ + super(DT_IIITN_WLS, self).__init__( + bvalues=bvalues, bounds=bounds, + initial_guess=initial_guess, thresholds=thresholds + ) + self.method = method.upper() + + def ivim_fit(self, signals, bvalues, **kwargs): + """Perform the IVIM fit using the selected method (WLS or RLM). + + Args: + signals (array-like): Signal intensities at each b-value. + bvalues (array-like, optional): b-values for the signals. + + Returns: + dict: Dictionary with keys "D", "f", "Dp". + """ + bvalues = np.array(bvalues) + + # Use threshold as cutoff if available + cutoff = 200 + if self.thresholds is not None and len(self.thresholds) > 0: + cutoff = self.thresholds[0] + + D, f, Dp = wls_ivim_fit(bvalues, signals, cutoff=cutoff, + method=self.method) + + results = {} + results["D"] = D + results["f"] = f + results["Dp"] = Dp + + return results diff --git a/src/standardized/ETP_SRI_LinearFitting.py b/src/standardized/ETP_SRI_LinearFitting.py index f219ff16..9adbb86c 100644 --- a/src/standardized/ETP_SRI_LinearFitting.py +++ b/src/standardized/ETP_SRI_LinearFitting.py @@ -1,6 +1,6 @@ import numpy as np from src.wrappers.OsipiBase import OsipiBase -from src.original.ETP_SRI.LinearFitting import LinearFit +from src.original.fitting.ETP_SRI.LinearFitting import LinearFit import warnings warnings.simplefilter('once', UserWarning) @@ -48,8 +48,8 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non super(ETP_SRI_LinearFitting, self).__init__(bvalues, thresholds, bounds, initial_guess) if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + self.use_bounds = {"f": False, "Dp": False, "D": False, "S0": False} + self.use_initial_guess = {"f": False, "Dp": False, "D": False, "S0": False} # Could be a good idea to have all the submission-specfic variable be # defined with initials? diff --git a/src/standardized/IAR_LU_biexp.py b/src/standardized/IAR_LU_biexp.py index ff54311c..4044513d 100644 --- a/src/standardized/IAR_LU_biexp.py +++ b/src/standardized/IAR_LU_biexp.py @@ -1,7 +1,7 @@ import numpy as np from dipy.core.gradients import gradient_table from src.wrappers.OsipiBase import OsipiBase -from src.original.IAR_LundUniversity.ivim_fit_method_biexp import IvimModelBiExp +from src.original.fitting.IAR_LundUniversity.ivim_fit_method_biexp import IvimModelBiExp class IAR_LU_biexp(OsipiBase): @@ -43,12 +43,18 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(IAR_LU_biexp, self).__init__(bvalues, thresholds, bounds, initial_guess) - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = {"f": False, "Dp": False, "D": False} + else: + self.use_bounds = {"f": True, "Dp": True, "D": True} + + if initial_guess is None: + self.use_initial_guess = {"f": False, "Dp": False, "D": False} + else: + self.use_initial_guess = {"f": True, "Dp": True, "D": True} + # Check the inputs - + # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) @@ -70,6 +76,11 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + + # Make sure bounds and initial guess conform to the algorithm requirements + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] if self.IAR_algorithm is None: if bvalues is None: @@ -81,7 +92,7 @@ def ivim_fit(self, signals, bvalues, **kwargs): bvec[:,2] = 1 gtab = gradient_table(bvalues, bvecs=bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelBiExp(gtab, bounds=self.bounds, initial_guess=self.initial_guess) + self.IAR_algorithm = IvimModelBiExp(gtab, bounds=bounds, initial_guess=initial_guess) fit_results = self.IAR_algorithm.fit(signals) @@ -103,7 +114,10 @@ def ivim_fit_full_volume(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ - + # Make sure bounds and initial guess conform to the algorithm requirements + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] if self.IAR_algorithm is None: if bvalues is None: bvalues = self.bvalues @@ -114,7 +128,7 @@ def ivim_fit_full_volume(self, signals, bvalues, **kwargs): bvec[:,2] = 1 gtab = gradient_table(bvalues, bvecs=bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelBiExp(gtab, bounds=self.bounds, initial_guess=self.initial_guess) + self.IAR_algorithm = IvimModelBiExp(gtab, bounds=bounds, initial_guess=initial_guess) b0_index = np.where(bvalues == 0)[0][0] mask = signals[...,b0_index]>0 fit_results = self.IAR_algorithm.fit(signals, mask=mask) diff --git a/src/standardized/IAR_LU_modified_mix.py b/src/standardized/IAR_LU_modified_mix.py index 5ff5c9aa..9af1b4e0 100644 --- a/src/standardized/IAR_LU_modified_mix.py +++ b/src/standardized/IAR_LU_modified_mix.py @@ -1,7 +1,7 @@ import numpy as np from dipy.core.gradients import gradient_table from src.wrappers.OsipiBase import OsipiBase -from src.original.IAR_LundUniversity.ivim_fit_method_modified_mix import IvimModelVP +from src.original.fitting.IAR_LundUniversity.ivim_fit_method_modified_mix import IvimModelVP class IAR_LU_modified_mix(OsipiBase): @@ -45,23 +45,25 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(IAR_LU_modified_mix, self).__init__(bvalues, thresholds, bounds, initial_guess) - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + + self.use_bounds = {"f": False, "Dp": False, "D": False} # This algorithm performs intermediate steps that generates initial guesses outside very constrainted bounds + self.use_initial_guess = {"f": False, "Dp": False, "D": False} # This algorithm does not use initial guesses # Additional options self.stochastic = True # Check the inputs - + # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) bvec[:,2] = 1 gtab = gradient_table(self.bvalues, bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelVP(gtab, bounds=self.bounds, rescale_results_to_mm2_s=True) + bounds = [[self.bounds["f"][0], self.bounds["Dp"][0]*1000, self.bounds["D"][0]*1000], + [self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]] + + self.IAR_algorithm = IvimModelVP(gtab, bounds=bounds, rescale_units=False, rescale_results_to_mm2_s=True) else: self.IAR_algorithm = None @@ -76,6 +78,9 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + + bounds = [[self.bounds["f"][0], self.bounds["Dp"][0]*1000, self.bounds["D"][0]*1000], + [self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]] if self.IAR_algorithm is None: if bvalues is None: @@ -87,9 +92,18 @@ def ivim_fit(self, signals, bvalues, **kwargs): bvec[:,2] = 1 gtab = gradient_table(bvalues, bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelVP(gtab, bounds=self.bounds, rescale_results_to_mm2_s=True) - - fit_results = self.IAR_algorithm.fit(signals) + self.IAR_algorithm = IvimModelVP(gtab, bounds=bounds, rescale_results_to_mm2_s=True) + + try: + fit_results = self.IAR_algorithm.fit(signals) + except np.linalg.LinAlgError as err: + if 'Singular matrix' in str(err): + # We might have a stochastic error. Try to re-run the fit once. + fit_results = self.IAR_algorithm.fit(signals) + else: + print(str(np.linalg.LinAlgError)) + + #f = fit_results.model_params[1] #Dstar = fit_results.model_params[2] diff --git a/src/standardized/IAR_LU_modified_topopro.py b/src/standardized/IAR_LU_modified_topopro.py index bf8b582f..aa019270 100644 --- a/src/standardized/IAR_LU_modified_topopro.py +++ b/src/standardized/IAR_LU_modified_topopro.py @@ -1,7 +1,7 @@ import numpy as np from dipy.core.gradients import gradient_table from src.wrappers.OsipiBase import OsipiBase -from src.original.IAR_LundUniversity.ivim_fit_method_modified_topopro import IvimModelTopoPro +from src.original.fitting.IAR_LundUniversity.ivim_fit_method_modified_topopro import IvimModelTopoPro class IAR_LU_modified_topopro(OsipiBase): @@ -44,10 +44,17 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(IAR_LU_modified_topopro, self).__init__(bvalues, thresholds, bounds, initial_guess) - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + + self.use_bounds = {"f" : False, "Dp": False, "D": False} # This algorithm performs intermediate steps that generates new bounds + self.use_initial_guess = {"f" : False, "Dp": False, "D": False} # This algorithm does not use initial guesses + + # Additional options + self.stochastic = True + + # Check the inputs + if self.bounds["Dp"][0] == self.bounds["D"][1]: + print('warning, bounds for D* and D are equal, this will likely cause fitting errors. Setting D_upper to 99 percent of D_upper') + self.bounds["D"][1] = self.bounds["D"][1]*0.99 # Check the inputs # Initialize the algorithm @@ -55,8 +62,11 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non bvec = np.zeros((self.bvalues.size, 3)) bvec[:,2] = 1 gtab = gradient_table(self.bvalues, bvec, b0_threshold=0) + + bounds = [[self.bounds["f"][0], self.bounds["Dp"][0]*1000, self.bounds["D"][0]*1000], + [self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]] - self.IAR_algorithm = IvimModelTopoPro(gtab, bounds=self.bounds, rescale_results_to_mm2_s=True) + self.IAR_algorithm = IvimModelTopoPro(gtab, bounds=bounds, rescale_results_to_mm2_s=True) else: self.IAR_algorithm = None @@ -71,6 +81,8 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + bounds = [[self.bounds["f"][0], self.bounds["Dp"][0]*1000, self.bounds["D"][0]*1000], + [self.bounds["f"][1], self.bounds["Dp"][1]*1000, self.bounds["D"][1]*1000]] if self.IAR_algorithm is None: if bvalues is None: @@ -82,7 +94,7 @@ def ivim_fit(self, signals, bvalues, **kwargs): bvec[:,2] = 1 gtab = gradient_table(bvalues, bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelTopoPro(gtab, bounds=self.bounds, rescale_results_to_mm2_s=True) + self.IAR_algorithm = IvimModelTopoPro(gtab, bounds=bounds, rescale_results_to_mm2_s=True) fit_results = self.IAR_algorithm.fit(signals) diff --git a/src/standardized/IAR_LU_segmented_2step.py b/src/standardized/IAR_LU_segmented_2step.py index c5be9b45..34998ad9 100644 --- a/src/standardized/IAR_LU_segmented_2step.py +++ b/src/standardized/IAR_LU_segmented_2step.py @@ -1,7 +1,7 @@ import numpy as np from dipy.core.gradients import gradient_table from src.wrappers.OsipiBase import OsipiBase -from src.original.IAR_LundUniversity.ivim_fit_method_segmented_2step import IvimModelSegmented2Step +from src.original.fitting.IAR_LundUniversity.ivim_fit_method_segmented_2step import IvimModelSegmented2Step class IAR_LU_segmented_2step(OsipiBase): @@ -43,19 +43,31 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(IAR_LU_segmented_2step, self).__init__(bvalues, thresholds, bounds, initial_guess) - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = {"f": False, "Dp": False, "D": False, "S0": False} + else: + self.use_bounds = {"f": True, "Dp": True, "D": True, "S0": True} + + if initial_guess is None: + self.use_initial_guess = {"f": False, "Dp": False, "D": False, "S0": False} + else: + self.use_initial_guess = {"f": True, "Dp": True, "D": True, "S0": True} + # Check the inputs + # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) bvec[:,2] = 1 gtab = gradient_table(self.bvalues, bvec, b0_threshold=0) - - self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=self.bounds, initial_guess=self.initial_guess, b_threshold=self.thresholds) + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \ + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + + # Adapt the initial guess to the format needed for the algorithm + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] + + self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=bounds, initial_guess=initial_guess, b_threshold=self.thresholds) else: self.IAR_algorithm = None @@ -70,7 +82,12 @@ def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs): Returns: _type_: _description_ """ - print(thresholds) + # Adapt the bounds to the format needed for the algorithm + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \ + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + + # Adapt the initial guess to the format needed for the algorithm + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] if self.IAR_algorithm is None: if bvalues is None: @@ -85,7 +102,7 @@ def ivim_fit(self, signals, bvalues, thresholds=None, **kwargs): if self.thresholds is None: self.thresholds = 200 - self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=self.bounds, initial_guess=self.initial_guess, b_threshold=self.thresholds) + self.IAR_algorithm = IvimModelSegmented2Step(gtab, bounds=bounds, initial_guess=initial_guess, b_threshold=self.thresholds) fit_results = self.IAR_algorithm.fit(signals) diff --git a/src/standardized/IAR_LU_segmented_3step.py b/src/standardized/IAR_LU_segmented_3step.py index 73aeb42b..96443e98 100644 --- a/src/standardized/IAR_LU_segmented_3step.py +++ b/src/standardized/IAR_LU_segmented_3step.py @@ -1,7 +1,7 @@ import numpy as np from dipy.core.gradients import gradient_table from src.wrappers.OsipiBase import OsipiBase -from src.original.IAR_LundUniversity.ivim_fit_method_segmented_3step import IvimModelSegmented3Step +from src.original.fitting.IAR_LundUniversity.ivim_fit_method_segmented_3step import IvimModelSegmented3Step class IAR_LU_segmented_3step(OsipiBase): @@ -43,17 +43,31 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(IAR_LU_segmented_3step, self).__init__(bvalues, thresholds, bounds, initial_guess) - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = {"f" : False, "Dp" : False, "D" : False, "S0" : False} + else: + self.use_bounds = {"f" : True, "Dp" : True, "D" : True, "S0" : True} + + if initial_guess is None: + self.use_initial_guess = {"f" : False, "Dp" : False, "D" : False, "S0" : False} + else: + self.use_initial_guess = {"f" : True, "Dp" : True, "D" : True, "S0" : True} + # Check the inputs + # Initialize the algorithm if self.bvalues is not None: bvec = np.zeros((self.bvalues.size, 3)) bvec[:,2] = 1 gtab = gradient_table(self.bvalues, bvec, b0_threshold=0) + + # Adapt the bounds to the format needed for the algorithm + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \ + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + + # Adapt the initial guess to the format needed for the algorithm + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] self.IAR_algorithm = IvimModelSegmented3Step(gtab, bounds=self.bounds, initial_guess=self.initial_guess) else: @@ -70,6 +84,12 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + # Adapt the bounds to the format needed for the algorithm + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \ + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + + # Adapt the initial guess to the format needed for the algorithm + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] if self.IAR_algorithm is None: if bvalues is None: @@ -81,7 +101,7 @@ def ivim_fit(self, signals, bvalues, **kwargs): bvec[:,2] = 1 gtab = gradient_table(bvalues, bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelSegmented3Step(gtab, bounds=self.bounds, initial_guess=self.initial_guess) + self.IAR_algorithm = IvimModelSegmented3Step(gtab, bounds=bounds, initial_guess=initial_guess) fit_results = self.IAR_algorithm.fit(signals) diff --git a/src/standardized/IAR_LU_subtracted.py b/src/standardized/IAR_LU_subtracted.py index abd38d63..0b75fd6a 100644 --- a/src/standardized/IAR_LU_subtracted.py +++ b/src/standardized/IAR_LU_subtracted.py @@ -1,7 +1,7 @@ import numpy as np from dipy.core.gradients import gradient_table from src.wrappers.OsipiBase import OsipiBase -from src.original.IAR_LundUniversity.ivim_fit_method_subtracted import IvimModelSubtracted +from src.original.fitting.IAR_LundUniversity.ivim_fit_method_subtracted import IvimModelSubtracted class IAR_LU_subtracted(OsipiBase): @@ -43,10 +43,15 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(IAR_LU_subtracted, self).__init__(bvalues, thresholds, bounds, initial_guess) - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + if bounds is None: + self.use_bounds = {"f" : False, "Dp" : False, "D" : False, "S0" : False} + else: + self.use_bounds = {"f" : False, "Dp" : True, "D" : True, "S0" : True} + if initial_guess is None: + self.use_initial_guess = {"f" : False, "Dp" : False, "D" : False, "S0" : False} + else: + self.use_initial_guess = {"f" : False, "Dp" : True, "D" : True, "S0" : True} + # Check the inputs # Initialize the algorithm @@ -54,8 +59,15 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non bvec = np.zeros((self.bvalues.size, 3)) bvec[:,2] = 1 gtab = gradient_table(self.bvalues, bvec, b0_threshold=0) + + # Adapt the bounds to the format needed for the algorithm + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \ + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + + # Adapt the initial guess to the format needed for the algorithm + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] - self.IAR_algorithm = IvimModelSubtracted(gtab, bounds=self.bounds, initial_guess=self.initial_guess) + self.IAR_algorithm = IvimModelSubtracted(gtab, bounds=bounds, initial_guess=initial_guess) else: self.IAR_algorithm = None @@ -70,6 +82,12 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + # Adapt the bounds to the format needed for the algorithm + bounds = [[self.bounds["S0"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["D"][0]], \ + [self.bounds["S0"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["D"][1]]] + + # Adapt the initial guess to the format needed for the algorithm + initial_guess = [self.initial_guess["S0"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["D"]] if self.IAR_algorithm is None: if bvalues is None: @@ -81,7 +99,7 @@ def ivim_fit(self, signals, bvalues, **kwargs): bvec[:,2] = 1 gtab = gradient_table(bvalues, bvec, b0_threshold=0) - self.IAR_algorithm = IvimModelSubtracted(gtab, bounds=self.bounds, initial_guess=self.initial_guess) + self.IAR_algorithm = IvimModelSubtracted(gtab, bounds=bounds, initial_guess=initial_guess) fit_results = self.IAR_algorithm.fit(signals) diff --git a/src/standardized/IVIM_NEToptim.py b/src/standardized/IVIM_NEToptim.py index ebf2c33f..c3cbdbe9 100644 --- a/src/standardized/IVIM_NEToptim.py +++ b/src/standardized/IVIM_NEToptim.py @@ -66,9 +66,22 @@ def initialize(self, bounds, initial_guess, fitS0, traindata, SNR, n): SNR = (5, 100) self.training_data(self.bvalues,n=n,SNR=SNR) self.arg=Arg() + print('note that the bounds in the network are soft bounds and implemented by a sigmoid transform. In order for the network to be sensitive over the range, we extend the bounds ny 30%') if bounds is not None: - self.arg.net_pars.cons_min = bounds[0] # Dt, Fp, Ds, S0 - self.arg.net_pars.cons_max = bounds[1] # Dt, Fp, Ds, S0 + self.bounds = bounds + else: + warnings.warn('No bounds indicated. default bounds are used of self.cons_min = [0, 0, 0.005, 0] and self.cons_max = [0.005, 1, 0.2, 2.0] # Dt, Fp, Ds, S0') + self.bounds = {"S0" : [0, 2], "f" : [0, 1], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} # These are defined as [lower, upper] + self.arg.net_pars.cons_min = np.array([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]])#bounds[0] # Dt, Fp, Ds, S0 + self.arg.net_pars.cons_max = np.array([self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]])#bounds[1] # Dt, Fp, Ds, S0 + + boundsrange = 0.3 * (np.array(self.arg.net_pars.cons_max)-np.array(self.arg.net_pars.cons_min)) # ensure that we are on the most lineair bit of the sigmoid function + self.arg.net_pars.cons_min = np.array(self.arg.net_pars.cons_min) - boundsrange + self.arg.net_pars.cons_max = np.array(self.arg.net_pars.cons_max) + boundsrange + self.bounds={"S0" : [self.arg.net_pars.cons_min[3], self.arg.net_pars.cons_max[3]], "f" : [self.arg.net_pars.cons_min[1], self.arg.net_pars.cons_max[1]], "Dp" : [self.arg.net_pars.cons_min[2], self.arg.net_pars.cons_max[2]], "D" : [self.arg.net_pars.cons_min[0], self.arg.net_pars.cons_max[0]]} # These are defined as [lower, upper] + + self.use_bounds = {"f": True, "Dp": True, "D": True} + self.use_initial_guess = {"f": False, "Dp": False, "D": False} if traindata is None: self.net = deep.learn_IVIM(self.train_data['data'], self.bvalues, self.arg) else: @@ -111,6 +124,10 @@ def ivim_fit_full_volume(self, signals, bvalues, retrain_on_input_data=False, ** """ 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") + minimum_bvalue = np.min(self.bvalues) # We normalize the signal to the minimum bvalue. Should be 0 or very close to 0. + b0_indices = np.where(self.bvalues == minimum_bvalue)[0] + normalization_factor = np.mean(signals[..., b0_indices],axis=-1) + signals = signals / np.repeat(normalization_factor[...,np.newaxis],np.shape(signals)[-1],-1) signals, shape = self.reshape_to_voxelwise(signals) if retrain_on_input_data: diff --git a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py index 11a9a944..4c9b10de 100644 --- a/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py +++ b/src/standardized/OGC_AmsterdamUMC_Bayesian_biexp.py @@ -1,5 +1,5 @@ from src.wrappers.OsipiBase import OsipiBase -from src.original.OGC_AmsterdamUMC.LSQ_fitting import flat_neg_log_prior, fit_bayesian, empirical_neg_log_prior, fit_segmented, fit_bayesian_array, fit_segmented_array +from src.original.fitting.OGC_AmsterdamUMC.LSQ_fitting import flat_neg_log_prior, fit_bayesian, empirical_neg_log_prior, fit_segmented, fit_bayesian_array, fit_segmented_array import numpy as np class OGC_AmsterdamUMC_Bayesian_biexp(OsipiBase): @@ -57,27 +57,13 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.fit_segmented=fit_segmented def initialize(self, bounds=None, initial_guess=None, fitS0=True, prior_in=None, thresholds=None): - - - if bounds is None: - print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]') - self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) - else: - self.bounds=bounds - if initial_guess is None: - print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') - self.initial_guess = [0.001, 0.001, 0.01, 1] - else: - self.initial_guess = initial_guess - self.use_initial_guess = True - self.use_bounds = True - if thresholds is None: - print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]') - thresholds = 150 + self.use_initial_guess = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} self.thresholds = thresholds + if prior_in is None: print('using a flat prior between bounds') - self.neg_log_prior=flat_neg_log_prior([self.bounds[0][0],self.bounds[1][0]],[self.bounds[0][1],self.bounds[1][1]],[self.bounds[0][2],self.bounds[1][2]],[self.bounds[0][3],self.bounds[1][3]]) + self.neg_log_prior=flat_neg_log_prior([self.bounds["D"][0],self.bounds["D"][1]],[self.bounds["f"][0],self.bounds["f"][1]],[self.bounds["Dp"][0],self.bounds["Dp"][1]],[self.bounds["S0"][0],self.bounds["S0"][1]]) else: print('warning, bounds are not used, as a prior is used instead') if len(prior_in) == 4: @@ -96,15 +82,20 @@ def ivim_fit(self, signals, bvalues, initial_guess=None, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + + initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] + bvalues=np.array(bvalues) epsilon = 0.000001 - fit_results = fit_segmented(bvalues, signals, bounds=self.bounds, cutoff=self.thresholds, p0=self.initial_guess) + fit_results = fit_segmented(bvalues, signals, bounds=bounds, cutoff=self.thresholds, p0=initial_guess) fit_results=np.array(fit_results+(1,)) for i in range(4): - if fit_results[i] < self.bounds[0][i] : fit_results[0] = self.bounds[0][i]+epsilon - if fit_results[i] > self.bounds[1][i] : fit_results[0] = self.bounds[1][i]-epsilon - fit_results = self.OGC_algorithm(bvalues, signals, self.neg_log_prior, x0=fit_results, fitS0=self.fitS0, bounds=self.bounds) + if fit_results[i] < bounds[0][i] : fit_results[0] = bounds[0][i]+epsilon + if fit_results[i] > bounds[1][i] : fit_results[0] = bounds[1][i]-epsilon + fit_results = self.OGC_algorithm(bvalues, signals, self.neg_log_prior, x0=fit_results, fitS0=self.fitS0, bounds=bounds) results = {} results["D"] = fit_results[0] @@ -121,6 +112,11 @@ def ivim_fit_full_volume(self, signals, bvalues, njobs=4, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + + initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] + # normalize signals # Get index of b=0 shape=np.shape(signals) @@ -139,7 +135,7 @@ def ivim_fit_full_volume(self, signals, bvalues, njobs=4, **kwargs): bvalues=np.array(bvalues) epsilon = 0.000001 - fit_results = np.array(fit_segmented_array(bvalues, signals, bounds=self.bounds, cutoff=self.thresholds, p0=self.initial_guess)) + fit_results = np.array(fit_segmented_array(bvalues, signals, bounds=bounds, cutoff=self.thresholds, p0=initial_guess)) #fit_results=np.array(fit_results+(1,)) # Loop over parameters (rows) @@ -147,11 +143,11 @@ def ivim_fit_full_volume(self, signals, bvalues, njobs=4, **kwargs): if i == 3: fit_results[i] = np.random.normal(1,0.2,np.shape(fit_results[i])) else: - below = fit_results[i] < self.bounds[0][i] - above = fit_results[i] > self.bounds[1][i] + below = fit_results[i] < bounds[0][i] + above = fit_results[i] > bounds[1][i] - fit_results[i, below] = self.bounds[0][i] + epsilon - fit_results[i, above] = self.bounds[1][i] - epsilon + fit_results[i, below] = bounds[0][i] + epsilon + fit_results[i, above] = bounds[1][i] - epsilon self.jobs=njobs fit_results = self.OGC_algorithm_array(bvalues, signals,fit_results, self) diff --git a/src/standardized/OGC_AmsterdamUMC_biexp.py b/src/standardized/OGC_AmsterdamUMC_biexp.py index 0fce2d2f..80e5a8e0 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, fit_least_squares_array +from src.original.fitting.OGC_AmsterdamUMC.LSQ_fitting import fit_least_squares, fit_least_squares_array import numpy as np class OGC_AmsterdamUMC_biexp(OsipiBase): @@ -48,23 +48,8 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.OGC_algorithm = fit_least_squares self.OGC_algorithm_array = fit_least_squares_array self.fitS0=fitS0 - self.initialize(bounds, initial_guess, fitS0) - - def initialize(self, bounds, initial_guess, fitS0): - if bounds is None: - print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]') - self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) - else: - self.bounds=bounds - if initial_guess is None: - print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') - self.initial_guess = [0.001, 0.1, 0.01, 1] - else: - self.initial_guess = initial_guess - self.use_initial_guess = True - self.fitS0=fitS0 - self.use_initial_guess = True - self.use_bounds = True + self.use_initial_guess = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -76,9 +61,13 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + + initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] bvalues=np.array(bvalues) - fit_results = self.OGC_algorithm(bvalues, signals, p0=self.initial_guess, bounds=self.bounds, fitS0=self.fitS0) + fit_results = self.OGC_algorithm(bvalues, signals, p0=initial_guess, bounds=bounds, fitS0=self.fitS0) results = {} results["D"] = fit_results[0] diff --git a/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py b/src/standardized/OGC_AmsterdamUMC_biexp_segmented.py index aebf8224..c9dce7d9 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, fit_segmented_array +from src.original.fitting.OGC_AmsterdamUMC.LSQ_fitting import fit_segmented, fit_segmented_array import numpy as np class OGC_AmsterdamUMC_biexp_segmented(OsipiBase): @@ -45,25 +45,15 @@ 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) - + self.initialize(thresholds) - def initialize(self, bounds, initial_guess, thresholds): - if bounds is None: - print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]') - self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) - else: - self.bounds=bounds - if initial_guess is None: - print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') - self.initial_guess = [0.001, 0.001, 0.01, 1] - else: - self.initial_guess = initial_guess - self.use_initial_guess = True - self.use_bounds = True - if thresholds is None: + def initialize(self, thresholds): + self.use_initial_guess = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + + if self.thresholds is None: self.thresholds = 150 - print('warning, no thresholds were defined, so default bounds are used of 150') + print('warning, no thresholds were defined, so default threshold of 150 was used') else: self.thresholds = thresholds @@ -77,9 +67,13 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + + initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] bvalues=np.array(bvalues) - fit_results = self.OGC_algorithm(bvalues, signals, bounds=self.bounds, cutoff=self.thresholds, p0=self.initial_guess) + fit_results = self.OGC_algorithm(bvalues, signals, bounds=bounds, cutoff=self.thresholds, p0=initial_guess) results = {} results["D"] = fit_results[0] diff --git a/src/standardized/OJ_GU_bayesMATLAB.py b/src/standardized/OJ_GU_bayesMATLAB.py index c5c24238..889e8431 100644 --- a/src/standardized/OJ_GU_bayesMATLAB.py +++ b/src/standardized/OJ_GU_bayesMATLAB.py @@ -44,7 +44,10 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non """ #super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues, bounds, initial_guess, fitS0) super(OJ_GU_bayesMATLAB, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess) - self.initialize(bounds, initial_guess) + + self.use_initial_guess = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + if eng is None: print('initiating matlab; this may take some time. For repeated testing one could use the optional input eng as an already initiated matlab engine') self.eng=matlab.engine.start_matlab() @@ -53,32 +56,17 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.eng = eng self.keep_alive=True - def algorithm(self, Y, b, lim, blim): + def algorithm(self, Y, b, lim, blim, initial_guess): Y = matlab.double(Y.tolist()) - f = matlab.double(self.initial_guess[1]) - D = matlab.double(self.initial_guess[0]) - Dstar = matlab.double(self.initial_guess[2]) - S0 = matlab.double(self.initial_guess[3]) + f = matlab.double(initial_guess[1]) + D = matlab.double(initial_guess[0]) + Dstar = matlab.double(initial_guess[2]) + S0 = matlab.double(initial_guess[3]) b = matlab.double(b.tolist()) lim = matlab.double(lim.tolist()) out = self.eng.IVIM_bayes(Y, f, D, Dstar, S0, b, lim, nargout=1) return out['D']['mode'], out['f']['mode'], out['Dstar']['mode'], out['S0']['mode'] - def initialize(self, bounds,initial_guess): - if bounds is None: - print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]') - self.bounds=([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) - else: - self.bounds=bounds - if initial_guess is None: - print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') - self.initial_guess = [0.001, 0.1, 0.01, 1] - else: - self.initial_guess = initial_guess - self.use_initial_guess = True - self.use_initial_guess = True - self.use_bounds = True - def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -89,11 +77,16 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + + initial_guess = [self.initial_guess["D"], self.initial_guess["f"], self.initial_guess["Dp"], self.initial_guess["S0"]] fit_results = self.algorithm(np.array(signals)[:,np.newaxis], np.array(bvalues), - np.array(self.bounds)[:,[1,0,2,3]], - self.thresholds) + np.array(bounds)[:,[1,0,2,3]], + self.thresholds, + initial_guess) results = {} results["D"] = fit_results[0] diff --git a/src/standardized/OJ_GU_seg.py b/src/standardized/OJ_GU_seg.py index b5b33c11..9b8cbf2f 100644 --- a/src/standardized/OJ_GU_seg.py +++ b/src/standardized/OJ_GU_seg.py @@ -1,6 +1,6 @@ import numpy as np from src.wrappers.OsipiBase import OsipiBase -from src.original.OJ_GU.ivim_seg import seg +from src.original.fitting.OJ_GU.ivim_seg import seg class OJ_GU_seg(OsipiBase): """ @@ -42,10 +42,8 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non the requirements. """ super(OJ_GU_seg, self).__init__(bvalues, thresholds, bounds, initial_guess) - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + self.use_bounds = {"f" : False, "D" : False, "Dp" : False, "S0" : False} + self.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} # Check the inputs # Initialize the algorithm diff --git a/src/standardized/OJ_GU_segMATLAB.py b/src/standardized/OJ_GU_segMATLAB.py index abd9b16e..37b2399d 100644 --- a/src/standardized/OJ_GU_segMATLAB.py +++ b/src/standardized/OJ_GU_segMATLAB.py @@ -43,9 +43,9 @@ def __init__(self, bvalues=None, thresholds=200, bounds=None, initial_guess=None the requirements. """ #super(OGC_AmsterdamUMC_biexp, self).__init__(bvalues, bounds, initial_guess, fitS0) - super(OJ_GU_segMATLAB, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess) - self.use_initial_guess = False - self.initialize(bounds, thresholds) + super(OJ_GU_segMATLAB, self).__init__(bvalues=bvalues, bounds=bounds, initial_guess=initial_guess, thresholds=thresholds) + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} if eng is None: print('initiating matlab; this may take some time. For repeated testing one could use the optional input eng as an already initiated matlab engine') self.eng=matlab.engine.start_matlab() @@ -58,23 +58,11 @@ def algorithm(self, Y, b, lim, blim): Y = matlab.double(Y.tolist()) b = matlab.double(b.tolist()) lim = matlab.double(lim.tolist()) - blim = matlab.double(blim) + blim = matlab.double(float(blim)) results = self.eng.IVIM_seg(Y, b, lim, blim, False,nargout=3) (pars, mask, gof) = results return pars['D'], pars['f'], pars['Dstar'], pars['S0'] - def initialize(self, bounds, thresholds): - if bounds is None: - print('warning, no bounds were defined, so algorithm-specific default bounds are used') - self.bounds=([1e-6, 0, 0.003, 0],[0.003, 1.0, 0.2, 5]) - else: - self.bounds=bounds - self.use_bounds = True - if thresholds is None: - self.thresholds = 200 - print('warning, no thresholds were defined, so default bounds are used of 200') - else: - self.thresholds = thresholds def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -86,10 +74,12 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) fit_results = self.algorithm(np.array(signals)[:,np.newaxis], np.array(bvalues), - np.array(self.bounds)[:,[0,3,1,2]], + np.array(bounds)[:,[0,3,1,2]], self.thresholds) results = {} diff --git a/src/standardized/PV_MUMC_biexp.py b/src/standardized/PV_MUMC_biexp.py index 21e2d4a7..2cfa38b1 100644 --- a/src/standardized/PV_MUMC_biexp.py +++ b/src/standardized/PV_MUMC_biexp.py @@ -1,6 +1,6 @@ import numpy as np from src.wrappers.OsipiBase import OsipiBase -from src.original.PV_MUMC.two_step_IVIM_fit import fit_least_squares +from src.original.fitting.PV_MUMC.two_step_IVIM_fit import fit_least_squares class PV_MUMC_biexp(OsipiBase): @@ -39,10 +39,9 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non """ super(PV_MUMC_biexp, self).__init__(bvalues=bvalues, thresholds=thresholds, bounds=bounds, initial_guess=initial_guess) self.PV_algorithm = fit_least_squares - if bounds is not None: - print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} def ivim_fit(self, signals, bvalues=None): @@ -55,17 +54,19 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ + if self.bounds is None: + self.bounds = ([0.9, 0.0001, 0.0, 0.0025], [1.1, 0.003, 1, 0.2]) + else: + bounds = ([self.bounds["S0"][0], self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0]], + [self.bounds["S0"][1], self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1]]) if self.thresholds is None: self.thresholds = 200 - - if self.bounds is None: - self.bounds = ([0.9, 0.0001, 0.0, 0.0025], [1.1, 0.003, 1, 0.2]) DEFAULT_PARAMS = [0.003,0.1,0.05] try: - fit_results = self.PV_algorithm(bvalues, signals, bounds=self.bounds, cutoff=self.thresholds) + fit_results = self.PV_algorithm(bvalues, signals, bounds=bounds, cutoff=self.thresholds) except RuntimeError as e: if "maximum number of function evaluations" in str(e): fit_results = DEFAULT_PARAMS diff --git a/src/standardized/PvH_KB_NKI_IVIMfit.py b/src/standardized/PvH_KB_NKI_IVIMfit.py index c36c512a..8915d53c 100644 --- a/src/standardized/PvH_KB_NKI_IVIMfit.py +++ b/src/standardized/PvH_KB_NKI_IVIMfit.py @@ -1,5 +1,5 @@ from src.wrappers.OsipiBase import OsipiBase -from src.original.PvH_KB_NKI.DWI_functions_standalone import generate_IVIMmaps_standalone, generate_ADC_standalone +from src.original.fitting.PvH_KB_NKI.DWI_functions_standalone import generate_IVIMmaps_standalone, generate_ADC_standalone import numpy as np import warnings warnings.simplefilter('once', UserWarning) @@ -48,8 +48,8 @@ def __init__(self, bvalues=None, thresholds=None,bounds=None,initial_guess=None) self.NKI_algorithm = generate_IVIMmaps_standalone if bounds is not None: print('warning, bounds from wrapper are not (yet) used in this algorithm') - self.use_bounds = False - self.use_initial_guess = False + self.use_bounds = {"f" : False, "D" : False, "Dp" : False, "S0" : False} + self.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} def ivim_fit(self, signals, bvalues=None): diff --git a/src/standardized/Super_IVIM_DC.py b/src/standardized/Super_IVIM_DC.py index a5298381..cc853e13 100644 --- a/src/standardized/Super_IVIM_DC.py +++ b/src/standardized/Super_IVIM_DC.py @@ -59,8 +59,8 @@ def initialize(self, bounds, initial_guess, fitS0, SNR, working_dir=os.getcwd(), 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.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} + self.use_bounds = {"f" : False, "D" : False, "Dp" : False, "S0" : False} self.deep_learning = True self.supervised = True diff --git a/src/standardized/TCML_TechnionIIT_SLS.py b/src/standardized/TCML_TechnionIIT_SLS.py index e2647237..1a07c444 100644 --- a/src/standardized/TCML_TechnionIIT_SLS.py +++ b/src/standardized/TCML_TechnionIIT_SLS.py @@ -1,7 +1,7 @@ from src.wrappers.OsipiBase import OsipiBase from super_ivim_dc.source.Classsic_ivim_fit import IVIM_fit_sls import numpy as np - +import warnings class TCML_TechnionIIT_SLS(OsipiBase): """ TCML_TechnionIIT_lsqlm fitting algorithm by Angeleene Ang, Moti Freiman and Noam Korngut, TechnionIIT @@ -47,21 +47,17 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.initialize(bounds, fitS0,thresholds) def initialize(self, bounds, fitS0,thresholds): - if bounds is None: - print('warning, only D* is bounded between 0.001 and 0.5)') - self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3]) - else: - print('warning, although bounds are given, only D* is bounded)') - bounds=bounds - self.bounds = bounds + self.use_bounds = {"f": False, "Dp": False, "D": False} + warnings.warn('bounds are only used for initialization fit') + + self.use_initial_guess = {"f": False, "Dp": False, "D": False} + self.fitS0=fitS0 - self.use_bounds = False if thresholds is None: self.thresholds = 150 print('warning, no thresholds were defined, so default bounds are used of 150') else: self.thresholds = thresholds - self.use_initial_guess = False def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -75,15 +71,26 @@ def ivim_fit(self, signals, bvalues, **kwargs): """ bvalues=np.array(bvalues) - bounds=np.array(self.bounds) - bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]] + bounds = ([self.bounds["D"][0], self.bounds["Dp"][0], self.bounds["f"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["Dp"][1], self.bounds["f"][1], self.bounds["S0"][1]]) signals[signals<0]=0 fit_results = self.fit_least_squares(np.array(signals)[:,np.newaxis],bvalues, bounds, min_bval_high=self.thresholds) + def get_scalar(val): + """Convert value to Python scalar, handling numpy arrays.""" + if isinstance(val, np.ndarray): + return float(val.item()) + return float(val) + results = {} - results["D"] = fit_results[0] - results["f"] = fit_results[2] - results["Dp"] = fit_results[1] + if fit_results[0].size > 0: + results["D"] = get_scalar(fit_results[0]) + results["f"] = get_scalar(fit_results[2]) + results["Dp"] = get_scalar(fit_results[1]) + else: + results["D"] = 0 + results["f"] = 0 + results["Dp"] = 0 return results \ No newline at end of file diff --git a/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py b/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py index d1bc1254..212a2cd0 100644 --- a/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py +++ b/src/standardized/TCML_TechnionIIT_lsqBOBYQA.py @@ -49,20 +49,15 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non def initialize(self, bounds, initial_guess, fitS0): if bounds is None: - print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]') - self.bounds = ([0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]) + self.use_bounds = {"f": False, "Dp": False, "D": False} else: - bounds=bounds - self.bounds = bounds + self.use_bounds = {"f": True, "Dp": True, "D": True} + if initial_guess is None: - print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.001, 0.01, 1]') - self.initial_guess = [0.001, 0.1, 0.02, 1] # D, Dp, f, S0 + self.use_initial_guess = {"f": False, "Dp": False, "D": False} else: - self.initial_guess = initial_guess - self.use_initial_guess = True + self.use_initial_guess = {"f": True, "Dp": True, "D": True} self.fitS0=fitS0 - self.use_initial_guess = True - self.use_bounds = True def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -76,22 +71,28 @@ def ivim_fit(self, signals, bvalues, **kwargs): """ bvalues=np.array(bvalues) - bounds=np.array(self.bounds) - bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]] - initial_guess = np.array(self.initial_guess) - initial_guess = initial_guess[[0, 2, 1, 3]] + bounds = ([self.bounds["D"][0], self.bounds["Dp"][0], self.bounds["f"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["Dp"][1], self.bounds["f"][1], self.bounds["S0"][1]]) + initial_guess = [self.initial_guess["D"], self.initial_guess["Dp"], self.initial_guess["f"], self.initial_guess["S0"]] fit_results = self.fit_least_squares(bvalues, np.array(signals)[:,np.newaxis], bounds, initial_guess.copy()) + def get_scalar(val): + """Convert value to Python scalar, handling numpy arrays.""" + if isinstance(val, np.ndarray): + return float(val.item()) + return float(val) + results = {} - if fit_results[0].size == 0: - results["D"] = initial_guess[0] - results["f"] = initial_guess[2] - results["Dp"] = initial_guess[1] + if fit_results[0].size > 0: + results["D"] = get_scalar(fit_results[0]) + results["f"] = get_scalar(fit_results[2]) + results["Dp"] = get_scalar(fit_results[1]) else: - results["D"] = fit_results[0] - results["f"] = fit_results[2] - results["Dp"] = fit_results[1] + results["D"] = 0 + results["f"] = 0 + results["Dp"] = 0 + results = self.D_and_Ds_swap(results) return results \ No newline at end of file diff --git a/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py b/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py index 864298a0..a80bb2e7 100644 --- a/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py +++ b/src/standardized/TCML_TechnionIIT_lsq_sls_BOBYQA.py @@ -48,20 +48,14 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.initialize(bounds, fitS0, thresholds) def initialize(self, bounds, fitS0, thresholds): - if bounds is None: - print('warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 1.0,0.04, 3])') - self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 1.0 ,0.04, 3]) - else: - bounds=bounds - self.bounds = bounds - if thresholds is None: + if self.thresholds is None: self.thresholds = 200 print('warning, no thresholds were defined, so default bounds are used of 200') else: self.thresholds = thresholds self.fitS0=fitS0 - self.use_bounds = False - self.use_initial_guess = False + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + self.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -75,13 +69,25 @@ def ivim_fit(self, signals, bvalues, **kwargs): """ signals[signals<0]=0 bvalues=np.array(bvalues) - bounds=np.array(self.bounds) - bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]] + bounds = ([self.bounds["D"][0], self.bounds["Dp"][0], self.bounds["f"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["Dp"][1], self.bounds["f"][1], self.bounds["S0"][1]]) + + def get_scalar(val): + """Convert value to Python scalar, handling numpy arrays.""" + if isinstance(val, np.ndarray): + return float(val.item()) + return float(val) + fit_results = self.fit_least_squares(np.array(signals)[:,np.newaxis],bvalues, bounds,min_bval_high=self.thresholds) results = {} - results["D"] = fit_results[0] - results["f"] = fit_results[2] - results["Dp"] = fit_results[1] + if fit_results[0].size > 0: + results["D"] = get_scalar(fit_results[0]) + results["f"] = get_scalar(fit_results[2]) + results["Dp"] = get_scalar(fit_results[1]) + else: + results["D"] = 0 + results["f"] = 0 + results["Dp"] = 0 return results \ No newline at end of file diff --git a/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py b/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py index 573d0af8..e72a16a6 100644 --- a/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py +++ b/src/standardized/TCML_TechnionIIT_lsq_sls_lm.py @@ -1,6 +1,7 @@ from src.wrappers.OsipiBase import OsipiBase from super_ivim_dc.source.Classsic_ivim_fit import IVIM_fit_sls_lm import numpy as np +import warnings class TCML_TechnionIIT_lsq_sls_lm(OsipiBase): """ @@ -48,21 +49,16 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.initialize(bounds, fitS0,thresholds) def initialize(self, bounds, fitS0,thresholds): - if bounds is None: - print( - 'warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3])') - self.bounds = ([0.0003, 0.001, 0.009, 0], [0.008, 0.5, 0.04, 3]) - else: - bounds = bounds - self.bounds = bounds + self.use_bounds = {"f": False, "Dp": False, "D": False} + warnings.warn('bounds are only used for initialization fit') + if thresholds is None: self.thresholds = 150 print('warning, no thresholds were defined, so default bounds are used of 150') else: self.thresholds = thresholds self.fitS0=fitS0 - self.use_bounds = False - self.use_initial_guess = False + self.use_initial_guess = {"f": False, "Dp": False, "D": False} def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -76,13 +72,21 @@ def ivim_fit(self, signals, bvalues, **kwargs): """ signals[signals<0]=0 bvalues=np.array(bvalues) - fit_results = self.fit_least_squares(np.array(signals)[:,np.newaxis],bvalues, self.bounds, min_bval_high=self.thresholds) + bounds = ([self.bounds["D"][0], self.bounds["Dp"][0], self.bounds["f"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["Dp"][1], self.bounds["f"][1], self.bounds["S0"][1]]) + fit_results = self.fit_least_squares(np.array(signals)[:,np.newaxis],bvalues, bounds, min_bval_high=self.thresholds) + + def get_scalar(val): + """Convert value to Python scalar, handling numpy arrays.""" + if isinstance(val, np.ndarray): + return float(val.item()) + return float(val) results = {} if fit_results[0].size > 0: - results["D"] = fit_results[0] - results["f"] = fit_results[2] - results["Dp"] = fit_results[1] + results["D"] = get_scalar(fit_results[0]) + results["f"] = get_scalar(fit_results[2]) + results["Dp"] = get_scalar(fit_results[1]) else: results["D"] = 0 results["f"] = 0 diff --git a/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py b/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py index 5dcc72ca..bd37628b 100644 --- a/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py +++ b/src/standardized/TCML_TechnionIIT_lsq_sls_trf.py @@ -49,19 +49,17 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non def initialize(self, bounds, fitS0, thresholds): if bounds is None: - print('warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 1.0,0.04, 3])') - self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 1.0,0.04, 3]) + self.use_bounds = {"f": False, "Dp": False, "D": False} else: - bounds=bounds - self.bounds = bounds + self.use_bounds = {"f": True, "Dp": True, "D": True} + self.use_initial_guess = {"f": False, "Dp": False, "D": False} + if thresholds is None: self.thresholds = 200 print('warning, no thresholds were defined, so default bounds are used of 200') else: self.thresholds = thresholds self.fitS0=fitS0 - self.use_bounds = False - self.use_initial_guess = False def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -75,13 +73,24 @@ def ivim_fit(self, signals, bvalues, **kwargs): """ signals[signals<0]=0 bvalues=np.array(bvalues) - bounds=np.array(self.bounds) - bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]] + bounds = ([self.bounds["D"][0], self.bounds["Dp"][0], self.bounds["f"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["Dp"][1], self.bounds["f"][1], self.bounds["S0"][1]]) fit_results = self.fit_least_squares(np.array(signals)[:,np.newaxis],bvalues, bounds,min_bval_high=self.thresholds) + def get_scalar(val): + """Convert value to Python scalar, handling numpy arrays.""" + if isinstance(val, np.ndarray): + return float(val.item()) + return float(val) + results = {} - results["D"] = fit_results[0] - results["f"] = fit_results[2] - results["Dp"] = fit_results[1] + if fit_results[0].size > 0: + results["D"] = get_scalar(fit_results[0]) + results["f"] = get_scalar(fit_results[2]) + results["Dp"] = get_scalar(fit_results[1]) + else: + results["D"] = 0 + results["f"] = 0 + results["Dp"] = 0 return results \ No newline at end of file diff --git a/src/standardized/TCML_TechnionIIT_lsqlm.py b/src/standardized/TCML_TechnionIIT_lsqlm.py index b542b513..429fabcf 100644 --- a/src/standardized/TCML_TechnionIIT_lsqlm.py +++ b/src/standardized/TCML_TechnionIIT_lsqlm.py @@ -48,15 +48,13 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non self.initialize(bounds, initial_guess, fitS0) def initialize(self, bounds, initial_guess, fitS0): + self.use_bounds = {"f": False, "Dp": False, "D": False} + if initial_guess is None: - print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.1, 0.01, 1]') - self.initial_guess = [0.001, 0.1, 0.01, 1] + self.use_initial_guess = {"f": False, "Dp": False, "D": False} else: - self.initial_guess = initial_guess - self.use_initial_guess = True + self.use_initial_guess = {"f": True, "Dp": True, "D": True} self.fitS0=fitS0 - self.use_initial_guess = True - self.use_bounds = False def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -70,19 +68,25 @@ def ivim_fit(self, signals, bvalues, **kwargs): """ bvalues=np.array(bvalues) - initial_guess = np.array(self.initial_guess) - initial_guess = initial_guess[[0, 2, 1, 3]] + initial_guess = [self.initial_guess["D"], self.initial_guess["Dp"], self.initial_guess["f"], self.initial_guess["S0"]] fit_results = self.fit_least_squares(bvalues, np.array(signals)[:,np.newaxis], initial_guess) + def get_scalar(val): + """Convert value to Python scalar, handling numpy arrays.""" + if isinstance(val, np.ndarray): + return float(val.item()) + return float(val) + results = {} - if fit_results[0].size >0: - results["D"] = fit_results[0] - results["f"] = fit_results[2] - results["Dp"] = fit_results[1] + if fit_results[0].size > 0: + results["D"] = get_scalar(fit_results[0]) + results["f"] = get_scalar(fit_results[2]) + results["Dp"] = get_scalar(fit_results[1]) else: results["D"] = 0 results["f"] = 0 results["Dp"] = 0 + results = self.D_and_Ds_swap(results) return results \ No newline at end of file diff --git a/src/standardized/TCML_TechnionIIT_lsqtrf.py b/src/standardized/TCML_TechnionIIT_lsqtrf.py index 5307256a..64c0b6b3 100644 --- a/src/standardized/TCML_TechnionIIT_lsqtrf.py +++ b/src/standardized/TCML_TechnionIIT_lsqtrf.py @@ -49,20 +49,16 @@ def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=Non def initialize(self, bounds, initial_guess, fitS0): if bounds is None: - print('warning, no bounds were defined, so default bounds are used of ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3])') - self.bounds = ([0.0003, 0.001, 0.009, 0],[0.008, 0.5,0.04, 3]) + self.use_bounds = {"f": False, "Dp": False, "D": False} else: - bounds=bounds - self.bounds = bounds + self.use_bounds = {"f": True, "Dp": True, "D": True} + if initial_guess is None: - print('warning, no initial guesses were defined, so default bounds are used of [0.001, 0.1, 0.01, 1]') - self.initial_guess = [0.001, 0.1, 0.01, 1] # D, Dp, f, S0 + self.use_initial_guess = {"f": False, "Dp": False, "D": False} else: - self.initial_guess = initial_guess - self.use_initial_guess = True + self.use_initial_guess = {"f": True, "Dp": True, "D": True} + self.fitS0=fitS0 - self.use_initial_guess = True - self.use_bounds = True def ivim_fit(self, signals, bvalues, **kwargs): """Perform the IVIM fit @@ -74,18 +70,29 @@ def ivim_fit(self, signals, bvalues, **kwargs): Returns: _type_: _description_ """ + bounds = ([self.bounds["D"][0], self.bounds["Dp"][0], self.bounds["f"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["Dp"][1], self.bounds["f"][1], self.bounds["S0"][1]]) + initial_guess = [self.initial_guess["D"], self.initial_guess["Dp"], self.initial_guess["f"], self.initial_guess["S0"]] bvalues=np.array(bvalues) - bounds=np.array(self.bounds) - bounds=[bounds[0][[0, 2, 1, 3]], bounds[1][[0, 2, 1, 3]]] - initial_guess = np.array(self.initial_guess) - initial_guess = initial_guess[[0, 2, 1, 3]] fit_results = self.fit_least_squares(bvalues, np.array(signals)[:,np.newaxis], bounds,initial_guess) + def get_scalar(val): + """Convert value to Python scalar, handling numpy arrays.""" + if isinstance(val, np.ndarray): + return float(val.item()) + return float(val) + results = {} - results["D"] = fit_results[0] - results["f"] = fit_results[2] - results["Dp"] = fit_results[1] + if fit_results[0].size > 0: + results["D"] = get_scalar(fit_results[0]) + results["f"] = get_scalar(fit_results[2]) + results["Dp"] = get_scalar(fit_results[1]) + else: + results["D"] = 0 + results["f"] = 0 + results["Dp"] = 0 + results = self.D_and_Ds_swap(results) return results \ No newline at end of file diff --git a/src/standardized/TF_reference_IVIMfit.py b/src/standardized/TF_reference_IVIMfit.py index 146c5b75..3a70a29d 100644 --- a/src/standardized/TF_reference_IVIMfit.py +++ b/src/standardized/TF_reference_IVIMfit.py @@ -1,5 +1,5 @@ from src.wrappers.OsipiBase import OsipiBase -from src.original.TF_reference.segmented_IVIMfit import segmented_IVIM_fit +from src.original.fitting.TF_reference.segmented_IVIMfit import segmented_IVIM_fit import numpy as np class TF_reference_IVIMfit(OsipiBase): @@ -44,19 +44,14 @@ def __init__(self, bvalues=None, thresholds=200, bounds=None, initial_guess=None super(TF_reference_IVIMfit, self).__init__(bvalues=bvalues, thresholds=thresholds,bounds=bounds,initial_guess=initial_guess) self.TF_reference_algorithm = segmented_IVIM_fit self.initialize(bounds, thresholds) - self.use_initial_guess = False + self.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} def initialize(self, bounds, thresholds): - if bounds is None: - print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005],[0.005, 1.0, 0.2]') - self.bounds=([0, 0, 0.005, 0.8],[0.005, 1.0, 0.2, 1.2]) - else: - self.bounds=bounds - self.use_bounds = True - if thresholds is None: + self.use_bounds = {"f" : True, "D" : True, "Dp" : True, "S0" : True} + if self.thresholds is None: self.thresholds = 200 - print('warning, no thresholds were defined, so default threshold are used of 200') + print('warning, no thresholds were defined, so default threshold are used of 200') else: self.thresholds = thresholds @@ -71,7 +66,9 @@ def ivim_fit(self, signals, bvalues=None): Returns: _type_: _description_ """ - #bvalues = np.array(bvalues) + bounds = ([self.bounds["D"][0], self.bounds["f"][0], self.bounds["Dp"][0], self.bounds["S0"][0]], + [self.bounds["D"][1], self.bounds["f"][1], self.bounds["Dp"][1], self.bounds["S0"][1]]) + bvalues = np.array(bvalues) if np.any(signals < 0): @@ -79,7 +76,7 @@ def ivim_fit(self, signals, bvalues=None): print('warning, negative values in signal: values clipped to 0.01') - fit_results = self.TF_reference_algorithm(bvalues,signals,b_cutoff=self.thresholds, bounds=self.bounds) + fit_results = self.TF_reference_algorithm(bvalues,signals,b_cutoff=self.thresholds, bounds=bounds) results = {} results["D"] = fit_results[0] diff --git a/src/wrappers/OsipiBase.py b/src/wrappers/OsipiBase.py index b06203f2..6ef67db4 100644 --- a/src/wrappers/OsipiBase.py +++ b/src/wrappers/OsipiBase.py @@ -25,14 +25,24 @@ class OsipiBase: Diffusion b-values (s/mm²) matching the last dimension of the input data. thresholds : array-like, optional Thresholds used by specific algorithms (e.g., signal cutoffs). - bounds : array-like, optional - Parameter bounds for constrained optimization. - initial_guess : array-like, optional - Initial parameter estimates for the IVIM fit. + bounds : dict, optional + Parameter bounds for constrained optimization. Should be a dict with keys + like "S0", "f", "Dp", "D" and values as [lower, upper] lists or arrays. + E.g. {"S0" : [0.7, 1.3], "f" : [0, 1], "Dp" : [0.005, 0.2], "D" : [0, 0.005]}. + initial_guess : dict, optional + Initial parameter estimates for the IVIM fit. Should be a dict with keys + like "S0", "f", "Dp", "D" and float values. + E.g. {"S0" : 1, "f" : 0.1, "Dp" : 0.01, "D" : 0.001}. algorithm : str, optional Name of an algorithm module in ``src/standardized`` to load dynamically. If supplied, the instance is immediately converted to that algorithm’s subclass via :meth:`osipi_initiate_algorithm`. + force_default_settings : bool, optional + If bounds and initial guesses are not provided, the wrapper will set + them to reasonable physical ones, i.e. {"S0":[0.7, 1.3], "f":[0, 1], + "Dp":[0.005, 0.2], "D":[0, 0.005]}. + To prevent this, set this bool to False. Default initial guess + {"S0" : 1, "f": 0.1, "Dp": 0.01, "D": 0.001}. **kwargs Additional keyword arguments forwarded to the selected algorithm’s initializer if ``algorithm`` is provided. @@ -92,22 +102,40 @@ class OsipiBase: f_map = results["f"] """ - def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, algorithm=None, **kwargs): + def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, algorithm=None, force_default_settings=True, **kwargs): # Define the attributes as numpy arrays only if they are not None self.bvalues = np.asarray(bvalues) if bvalues is not None else None self.thresholds = np.asarray(thresholds) if thresholds is not None else None - self.bounds = np.asarray(bounds) if bounds is not None else None - 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.bounds = bounds if bounds is not None else None + self.initial_guess = initial_guess if initial_guess is not None else None + self.use_bounds = {"f" : False, "D" : False, "Dp" : False, "S0" : False} # Default to False. These are more specifically set by each algorithm subclass + self.use_initial_guess = {"f" : False, "D" : False, "Dp" : False, "S0" : False} # Default to False. These are more specifically set by each algorithm subclass self.deep_learning = False self.supervised = False self.stochastic = False + + if force_default_settings: + if self.bounds is None: + print('warning, no bounds were defined, so default bounds are used of [0, 0, 0.005, 0.7],[0.005, 1.0, 0.2, 1.3]') + self.bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} # These are defined as [lower, upper] + self.forced_default_bounds = True + + if self.initial_guess is None: + print('warning, no initial guesses were defined, so default initial guesses are used of [0.001, 0.001, 0.01, 1]') + self.initial_guess = {"S0" : 1, "f" : 0.1, "Dp" : 0.01, "D" : 0.001} + self.forced_default_initial_guess = True + + if self.thresholds is None: + self.thresholds = np.array([200]) + + self.osipi_bounds = self.bounds # Variable that stores the original bounds before they are passed to the algorithm + self.osipi_initial_guess = self.initial_guess # Variable that stores the original initial guesses before they are passed to the algorithm + self.id_ref = None # If the user inputs an algorithm to OsipiBase, it is intereprete as initiating # an algorithm object with that name. if algorithm: - self.osipi_initiate_algorithm(algorithm, bvalues=bvalues, thresholds=thresholds, bounds=bounds, initial_guess=initial_guess, **kwargs) + self.osipi_initiate_algorithm(algorithm, bvalues=self.bvalues, thresholds=self.thresholds, bounds=self.bounds, initial_guess=self.initial_guess, **kwargs) def osipi_initiate_algorithm(self, algorithm, **kwargs): """Turns the class into a specified one by the input. @@ -346,10 +374,12 @@ def osipi_fit_full_volume(self, data, bvalues=None, **kwargs): return results - except: + except Exception as e: # Check if the problem is that full volume fitting is simply not supported in the standardized implementation if not hasattr(self, "ivim_fit_full_volume"): #and callable(getattr(self, "ivim_fit_full_volume")): print("Full volume fitting not supported for this algorithm") + else: + print(f"Full volume fitting failed: {type(e).__name__}: {e}") return False @@ -501,7 +531,7 @@ def osipi_simple_bias_and_RMSE_test(self, SNR, bvalues, f, Dstar, D, noise_reali def D_and_Ds_swap(self,results): - if results['D']>results['Dp']: + if results['D']>results['Dp'] and results['Dp'] < 0.05: D=results['Dp'] results['Dp']=results['D'] results['D']=D diff --git a/src/wrappers/ivim_fit.py b/src/wrappers/ivim_fit.py index fe1f9dc6..268afe66 100644 --- a/src/wrappers/ivim_fit.py +++ b/src/wrappers/ivim_fit.py @@ -7,7 +7,7 @@ import numpy as np # from utils.data_simulation.GenerateData import GenerateData -from src.original.ETP_SRI.LinearFitting import LinearFit +from src.original.fitting.ETP_SRI.LinearFitting import LinearFit author_lookup = { 'ETP_SRI': LinearFit diff --git a/tests/IVIMmodels/unit_tests/algorithms.json b/tests/IVIMmodels/unit_tests/algorithms.json index 45ec0fea..83ad20e1 100644 --- a/tests/IVIMmodels/unit_tests/algorithms.json +++ b/tests/IVIMmodels/unit_tests/algorithms.json @@ -25,11 +25,18 @@ "OJ_GU_seg", "OJ_GU_segMATLAB", "OJ_GU_bayesMATLAB", - "TF_reference_IVIMfit" + "TF_reference_IVIMfit", + "DT_IIITN_WLS" ], + "TCML_TechnionIIT_lsqBOBYQA": { + "xfail_names": { + "pericardium": false, + "bounds": false + } + }, "IVIM_NEToptim": { "deep_learning": true, - "n":3000000 + "n": 3000000 }, "Super_IVIM_DC": { "deep_learning": true @@ -48,7 +55,9 @@ }, "ETP_SRI_LinearFitting": { "options": { - "thresholds": [500] + "thresholds": [ + 500 + ] }, "tolerances": { "rtol": { @@ -78,4 +87,4 @@ }, "test_bounds": false } -} +} \ No newline at end of file diff --git a/tests/IVIMmodels/unit_tests/compare.r b/tests/IVIMmodels/unit_tests/compare.r index 9dc3c6ad..6d6104ea 100644 --- a/tests/IVIMmodels/unit_tests/compare.r +++ b/tests/IVIMmodels/unit_tests/compare.r @@ -10,7 +10,13 @@ # 4. For the algorithm "IAR_LU_modified_mix" and "TCML_TechnionIIT_lsqtrf", replace the "f_f_alpha, Dp_f_alpha, D_f_alpha, f_t_alpha, Dp_t_alpha, D_t_alpha" columns with "0.01,0.01,0.01,0.0,0.0,0.0" # Exclude certain algorithms -exclude_algorithms <- c("IAR_LU_modified_mix", "TCML_TechnionIIT_lsqtrf") +exclude_algorithms <- c("ETP_SRI_LinearFitting", "IAR_LU_biexp", "IAR_LU_modified_topopro", + "IAR_LU_segmented_2step", "IAR_LU_segmented_3step", "IAR_LU_subtracted", + "OGC_AmsterdamUMC_Bayesian_biexp", "OGC_AmsterdamUMC_biexp", + "OGC_AmsterdamUMC_biexp_segmented", "PV_MUMC_biexp", + "TCML_TechnionIIT_lsq_sls_BOBYQA", "TCML_TechnionIIT_lsq_sls_lm", + "TCML_TechnionIIT_lsq_sls_trf", "TCML_TechnionIIT_lsqBOBYQA", + "TCML_TechnionIIT_lsqlm", "TCML_TechnionIIT_SLS") args = commandArgs(trailingOnly=TRUE) # Define file paths diff --git a/tests/IVIMmodels/unit_tests/generic.json b/tests/IVIMmodels/unit_tests/generic.json index c1e2477b..fc70018a 100644 --- a/tests/IVIMmodels/unit_tests/generic.json +++ b/tests/IVIMmodels/unit_tests/generic.json @@ -25,32 +25,6 @@ 0.07638861567746785 ] }, - "myocardium RV": { - "noise": 0.0005, - "D": 0.0024, - "f": 0.15, - "Dp": 0.08, - "data": [ - 1.000180079067396, - 0.986990380915803, - 0.972900789084442, - 0.939736386666685, - 0.8971605905354147, - 0.8399637991928016, - 0.8044883933349772, - 0.7570561134394189, - 0.7105776950887231, - 0.6690513540994412, - 0.5928512978405378, - 0.46634107416135884, - 0.36603261857375496, - 0.32608853910956437, - 0.22768988835218287, - 0.1574577830722027, - 0.1105906460374374, - 0.07645407094838856 - ] - }, "myocardium ra": { "noise": 0.0005, "D": 0.0015, @@ -77,58 +51,6 @@ 0.2068495366721375 ] }, - "Blood RV": { - "noise": 0.0005, - "D": 0.003, - "f": 1.0, - "Dp": 0.1, - "data": [ - 1.000603693073812, - 0.904606934487691, - 0.818063650190734, - 0.6061350763287074, - 0.36819110032265356, - 0.1355553150583588, - 0.05010509203622437, - 0.0072463289357294755, - 0.00018700717279331042, - -0.0003714271020912267, - 0.0005724345627937243, - 0.00027501426716422045, - 6.60209245021009e-05, - 0.0005070911042906482, - -0.0010201585810508762, - -0.00033545531560461706, - -4.138621780211343e-05, - -3.057038566732644e-05 - ] - }, - "Blood RA": { - "noise": 0.0005, - "D": 0.003, - "f": 1.0, - "Dp": 0.1, - "data": [ - 0.9996412591901256, - 0.9045050114102029, - 0.8192855804119286, - 0.6061658664563122, - 0.36827988782030663, - 0.1351204360331529, - 0.04933291369547591, - 0.0071928296039783035, - 0.0002920234672598372, - -0.0005199682835767715, - 0.00016067058613793012, - -0.00035291450109271123, - 0.0008226789593082577, - -0.0004336298974722816, - -0.000362292924476757, - -0.0005147600841361719, - 0.00019344887052810314, - 5.962153441482305e-05 - ] - }, "muscle": { "noise": 0.0005, "D": 0.00137, @@ -181,32 +103,6 @@ 0.19882602755160078 ] }, - "gall bladder": { - "noise": 0.0005, - "D": 0.003, - "f": 0.0, - "Dp": 0.1, - "data": [ - 0.9989060357343036, - 0.9970876206615882, - 0.9946410946403311, - 0.9855774147431635, - 0.9699884929094837, - 0.9414195673254752, - 0.914311705249997, - 0.8599304660085283, - 0.7985053406134563, - 0.7413790056650638, - 0.6389785194259883, - 0.47225570767682007, - 0.34942281984492723, - 0.30046305722115474, - 0.19300343886395055, - 0.12310767410545541, - 0.0777204745744959, - 0.049695219190825604 - ] - }, "esophagus": { "noise": 0.0005, "D": 0.00167, @@ -233,32 +129,6 @@ 0.12754291142997942 ] }, - "esophagus cont": { - "noise": 0.0005, - "D": 0.00167, - "f": 0.32, - "Dp": 0.03, - "data": [ - 1.0001332280689843, - 0.9895985875899938, - 0.9789478029216182, - 0.9499445641099479, - 0.9055147553106474, - 0.8327969031957817, - 0.7763844730026062, - 0.6969814423207864, - 0.6348857150053814, - 0.5912243532007254, - 0.5329955492992777, - 0.4477650171533658, - 0.37850963205686977, - 0.34877947584053537, - 0.27088516323851575, - 0.21121900341654684, - 0.1648504613814509, - 0.12839584718860902 - ] - }, "st wall": { "noise": 0.0005, "D": 0.0015, @@ -285,32 +155,6 @@ 0.15590509909570116 ] }, - "Stomach Contents": { - "noise": 0.0005, - "D": 0.003, - "f": 0.0, - "Dp": 0.0, - "data": [ - 1.0000580074394811, - 0.9970555713174671, - 0.9938937097177735, - 0.9848106471664809, - 0.9705731199789829, - 0.9421815501092592, - 0.9140157968535895, - 0.8605459524785368, - 0.7975580576515555, - 0.7412702572219417, - 0.636994314232129, - 0.4723024757673014, - 0.35000439003580075, - 0.30174809044287926, - 0.19203748494882025, - 0.12286178144346882, - 0.07815250033798066, - 0.05061912506381041 - ] - }, "pancreas": { "noise": 0.0005, "D": 0.0013, @@ -389,58 +233,6 @@ 0.10356078464838264 ] }, - "Left kidney cortex": { - "noise": 0.0005, - "D": 0.00212, - "f": 0.097, - "Dp": 0.02, - "data": [ - 1.0011945854174855, - 0.9955396602100857, - 0.9918214155056828, - 0.9812684164210659, - 0.963098747825943, - 0.930912664496857, - 0.9000687778422743, - 0.8484119945257521, - 0.7920259614209312, - 0.7434277758611111, - 0.6614289565458207, - 0.5323474665735095, - 0.4293903595942227, - 0.38651481842000707, - 0.2821716436119705, - 0.20476916115368815, - 0.14953578190775682, - 0.1076376696836249 - ] - }, - "left kidney medulla": { - "noise": 0.0005, - "D": 0.00209, - "f": 0.158, - "Dp": 0.019, - "data": [ - 0.999784883468332, - 0.9956275637829168, - 0.9897288582217845, - 0.9772893183868882, - 0.9566347399935364, - 0.9148307724225175, - 0.8794381537213443, - 0.8201110314907877, - 0.7567369118787439, - 0.7062127395964859, - 0.625573903050984, - 0.5012834077932828, - 0.4049049660412132, - 0.3642551567909775, - 0.2671478449807342, - 0.1948282799148607, - 0.14272699354254856, - 0.10447427993309061 - ] - }, "spleen": { "noise": 0.0005, "D": 0.0013, @@ -519,58 +311,6 @@ 0.5563023125518155 ] }, - "Artery": { - "noise": 0.0005, - "D": 0.003, - "f": 1.0, - "Dp": 0.1, - "data": [ - 0.9997800501422142, - 0.9045819629490028, - 0.818088444799732, - 0.6062531634694728, - 0.36835081010301113, - 0.13565643887731554, - 0.04941515572602334, - 0.006047036365649863, - 0.000358653575991588, - -0.00032459849416059477, - -0.0007608227498529157, - 5.1438393887969995e-05, - 0.00019454498032650262, - -6.96219775034424e-05, - 0.0004213480781055518, - -0.0007729334158231029, - -0.0002834467902361242, - 0.00037229679238038097 - ] - }, - "Vein": { - "noise": 0.0005, - "D": 0.003, - "f": 1.0, - "Dp": 0.1, - "data": [ - 1.0001149665130338, - 0.9052158548961062, - 0.8192496016137271, - 0.6069001173171487, - 0.36753116759462695, - 0.13519606730978775, - 0.04965233826725912, - 0.006764269797673213, - 0.00030705658106631984, - 6.365223281603042e-05, - -7.519056957567633e-05, - -3.61461581814404e-05, - 3.0156761329221312e-05, - -0.0003928484200171012, - 0.0004656170878741209, - -9.935576265301419e-05, - -0.00013896810647857585, - 0.00022133391960194875 - ] - }, "asc lower intestine": { "noise": 0.0005, "D": 0.00131, @@ -597,84 +337,6 @@ 0.08487549935755932 ] }, - "trans lower intestine": { - "noise": 0.0005, - "D": 0.00131, - "f": 0.69, - "Dp": 0.029, - "data": [ - 0.9997303817205065, - 0.9794580744935948, - 0.9604711875831552, - 0.9050086591438171, - 0.8218151851876994, - 0.6890179954687737, - 0.5865846418363266, - 0.4519190479283628, - 0.36000260876813217, - 0.31008144191779136, - 0.26376048077496717, - 0.223671979602434, - 0.1960893389844811, - 0.1838359226391205, - 0.15162732015321162, - 0.12426136226164565, - 0.10201646999685307, - 0.08268553179392053 - ] - }, - "desc lower intestine": { - "noise": 0.0005, - "D": 0.00131, - "f": 0.69, - "Dp": 0.029, - "data": [ - 0.9998169952297454, - 0.9794581567785913, - 0.9611748946566195, - 0.90458376353294, - 0.8222853652765572, - 0.6884864031834491, - 0.5867353229852139, - 0.45199611132761436, - 0.3592817956229402, - 0.30944572861898323, - 0.2628024541204303, - 0.2236254487524024, - 0.19613360547699618, - 0.18238665571578985, - 0.15066862880035628, - 0.12406987012052711, - 0.10222792937336041, - 0.08376298023754827 - ] - }, - "small intestine": { - "noise": 0.0005, - "D": 0.00131, - "f": 0.69, - "Dp": 0.029, - "data": [ - 1.000672478806307, - 0.9801710601503958, - 0.9609114603189408, - 0.9038640653485989, - 0.8217913958709209, - 0.6874638577111291, - 0.5873916251429048, - 0.45172009389536083, - 0.35931580081980474, - 0.3102674128607682, - 0.263445124824966, - 0.22333874634028628, - 0.19514824524892244, - 0.18379440735891486, - 0.15068757445687012, - 0.12373605827586043, - 0.10115024125349835, - 0.08396227376915624 - ] - }, "pericardium": { "noise": 0.0005, "D": 0.003, diff --git a/tests/IVIMmodels/unit_tests/generic_DL.json b/tests/IVIMmodels/unit_tests/generic_DL.json index 46fd4944..106eac9c 100644 --- a/tests/IVIMmodels/unit_tests/generic_DL.json +++ b/tests/IVIMmodels/unit_tests/generic_DL.json @@ -5,54 +5,24 @@ "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 + 1.0098540139680026, + 0.9826939123364847, + 0.9680070551127059, + 0.9051951923775081, + 0.8558712574646193, + 0.8420463802228705, + 0.8405679561261958, + 0.7634791085579523, + 0.6918665709339131, + 0.6147515040309017, + 0.5785075121314839, + 0.4553468940020642, + 0.37626344938607714, + 0.328749789527815, + 0.2116297275844832, + 0.21399002904014153, + 0.18214686306523659, + 0.01937869081337886 ] }, "myocardium ra": { @@ -61,82 +31,24 @@ "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 + 1.0406717995505752, + 0.9501960180486319, + 1.053868272098131, + 0.9679990715927623, + 1.0053632512411972, + 0.8836365944117994, + 0.9230502523164995, + 0.8765208001873821, + 0.8064797376133074, + 0.7736466352849087, + 0.6856536025758, + 0.7091736978193912, + 0.4342890792845645, + 0.4836751055688441, + 0.4511202880668142, + 0.23151897852233178, + 0.26413406887522073, + 0.15459006766586506 ] }, "muscle": { @@ -145,26 +57,24 @@ "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 + 1.002603033075884, + 0.9697360475077582, + 0.9795040118515252, + 0.9144040832910203, + 1.0115515328731417, + 0.9544652319614496, + 0.9182601488335194, + 0.8127574832669384, + 0.8739770272527798, + 0.8467281293101417, + 0.7173371036356685, + 0.6185480801480356, + 0.5095700458236414, + 0.48659301101208124, + 0.505469597106218, + 0.3033780704919908, + 0.2628760533454496, + 0.18774252312454337 ] }, "Liver": { @@ -173,54 +83,24 @@ "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 + 0.9884429789591908, + 0.9364813776699068, + 1.017978988221523, + 0.9510342981269209, + 0.9458563978875824, + 0.8883314166489155, + 0.8533194464586843, + 0.8302164960355931, + 0.7734749003137477, + 0.8311216114001766, + 0.7092601306337021, + 0.6268086969671146, + 0.543013461267962, + 0.4700438190784427, + 0.36784365516451295, + 0.3817987327654102, + 0.21597570817371642, + 0.21780064409196245 ] }, "esophagus": { @@ -229,54 +109,24 @@ "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 + 0.9453739480172523, + 0.9658432787870418, + 1.0095178489625123, + 0.9843230112111447, + 0.9245205202278282, + 0.8446701527931184, + 0.7963335842379469, + 0.7348888004132484, + 0.6528562297380444, + 0.6098156706047, + 0.4791553053695655, + 0.4714487407128129, + 0.3105174344223318, + 0.35224455597646603, + 0.2966866495979564, + 0.1611496451188375, + 0.12919616833642522, + 0.09080054828251286 ] }, "st wall": { @@ -285,54 +135,24 @@ "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 + 0.9881089861735862, + 0.9526212869034709, + 1.0382604641164013, + 0.9591072967885832, + 0.982713925117592, + 0.9504506484852389, + 0.9397546838380895, + 0.8579851977895471, + 0.7453652276796091, + 0.6704700368847054, + 0.5655906050023686, + 0.46367295074521253, + 0.39110883912174815, + 0.39362684623348426, + 0.28837557771018163, + 0.30764680017011986, + 0.25064293584150205, + 0.13316445361515178 ] }, "pancreas": { @@ -341,26 +161,24 @@ "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 + 1.031005453873397, + 0.9901817126255473, + 1.0082066315395422, + 0.9441472886126179, + 0.9353029055134368, + 0.8972456228848986, + 0.8982432576951551, + 0.9146653983836379, + 0.7737500794698224, + 0.7730491379226242, + 0.6978302676592958, + 0.675307660952986, + 0.6136795150811432, + 0.5500125387723012, + 0.4656019820449028, + 0.3596100369605809, + 0.24935521335582497, + 0.26104896321612236 ] }, "Right kydney cortex": { @@ -369,26 +187,24 @@ "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 + 0.9948033560686549, + 0.986269713750878, + 0.9397752806414925, + 0.9424957795622789, + 0.9595550108567424, + 0.9428145694334725, + 0.9202423909344633, + 0.7836665291632042, + 0.7275806668224107, + 0.7254592526843646, + 0.7533707339222903, + 0.5481221225439892, + 0.3921338118520829, + 0.39411493570480044, + 0.303774513598521, + 0.1837682658282955, + 0.11383643340575138, + 0.0982310009460541 ] }, "right kidney medulla": { @@ -397,82 +213,24 @@ "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 + 0.9812548675944992, + 0.9908049827172921, + 0.9701478742658647, + 0.9493659268750785, + 0.9742827875265091, + 0.8964276645413026, + 0.9021007492417675, + 0.8030317677655417, + 0.7586769363402655, + 0.7571208886988315, + 0.5532225720254755, + 0.4965118626780167, + 0.38457748984323675, + 0.3569660047412554, + 0.25540586581148983, + 0.1485964868988466, + 0.11002330104255179, + 0.05744180163104591 ] }, "spleen": { @@ -481,26 +239,24 @@ "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 + 0.966630886462939, + 1.0215725421746968, + 1.0354232399573955, + 0.9653166260970993, + 1.0382021020651773, + 0.88224207040183, + 0.8803930942169789, + 0.7917365451539088, + 0.7743593728226801, + 0.6866107350534826, + 0.5942038433732073, + 0.5660665700273081, + 0.5466455658062289, + 0.4297892421622047, + 0.40176105675693796, + 0.35519638480876203, + 0.27895557515778524, + 0.1794380934707499 ] }, "spinal cord": { @@ -509,26 +265,24 @@ "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 + 0.9740635396857769, + 0.99214823024292, + 1.0257992938233047, + 0.9642726524712146, + 0.9719374765966681, + 0.8419904422325446, + 0.9477635081966547, + 0.8541094670097229, + 0.7628972813539163, + 0.8431770303771599, + 0.7038467061756126, + 0.7060696410758639, + 0.7406040532072716, + 0.7060258472060733, + 0.6434718066833011, + 0.546969841549462, + 0.45781202296717605, + 0.6070787905987006 ] }, "Bone Marrow": { @@ -537,82 +291,24 @@ "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 + 1.0633064158361891, + 0.9323204673437405, + 0.930170648853812, + 1.0106122223929555, + 0.9133555370818478, + 0.8877516328872553, + 0.8648021903254632, + 0.8689257480935776, + 0.8668168952058483, + 0.8488417803655424, + 0.7991775430354946, + 0.8049443481837053, + 0.714364056012283, + 0.7454394418907435, + 0.6689152726848362, + 0.6057937105295216, + 0.5306018259271807, + 0.5655482010588253 ] }, "asc lower intestine": { @@ -621,110 +317,24 @@ "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 + 0.8816247042444975, + 0.9895114117604342, + 0.9731426375339671, + 0.9102529862464518, + 0.7839768277304734, + 0.7033237432030581, + 0.5487033762979918, + 0.5047623845990575, + 0.3699527554349223, + 0.2935133424042648, + 0.20651277458785802, + 0.1703467799993676, + 0.24457367218880496, + 0.18781889068749386, + 0.13346993282347486, + 0.11803063994143033, + 0.11839824186436701, + 0.1821467677298994 ] }, "pericardium": { @@ -733,26 +343,24 @@ "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 + 1.010154692910326, + 0.973601290441154, + 1.032180388101306, + 1.0225584339225071, + 0.9711546836824555, + 0.9532524716292557, + 0.8953092179366884, + 0.8293789697476732, + 0.7320467984185076, + 0.7567778713426024, + 0.5594068992086532, + 0.3914884811901543, + 0.3461695407876617, + 0.31975693450905535, + 0.21503817282534457, + 0.14844832120833984, + 0.1357946941991588, + -0.00750137558340757 ] }, "config": { @@ -774,9 +382,7 @@ 550.0, 700.0, 850.0, - 1000.0, - 1100.0, - 1200.0 + 1000.0 ] } } \ No newline at end of file diff --git a/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py b/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py index 0761284d..6df9cbbb 100644 --- a/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py +++ b/tests/IVIMmodels/unit_tests/simple_test_run_of_algorithm.py @@ -17,7 +17,7 @@ ## Simple test code... # Used to just do a test run of an algorithm during development def dev_test_run(model, **kwargs): - bvalues = np.array([0, 50, 100, 150, 200, 500, 800]) + bvalues = np.array([0, 50, 0, 100, 0, 150, 200, 500, 0, 800]) def ivim_model(b, S0=1, f=0.1, Dstar=0.01, D=0.001): return S0*(f*np.exp(-b*Dstar) + (1-f)*np.exp(-b*D)) @@ -29,14 +29,14 @@ def ivim_model(b, S0=1, f=0.1, Dstar=0.01, D=0.001): #model = ETP_SRI_LinearFitting(thresholds=[200]) if kwargs: - results = model.osipi_fit_full_volume(signals, bvalues, **kwargs) + results = model.osipi_fit(signals, bvalues, **kwargs) else: - results = model.osipi_fit_full_volume(signals, bvalues) + results = model.osipi_fit(signals, bvalues) print(results) #test = model.osipi_simple_bias_and_RMSE_test(SNR=20, bvalues=bvalues, f=0.1, Dstar=0.03, D=0.001, noise_realizations=10) #model1 = ETP_SRI_LinearFitting(thresholds=[200]) -model2 = IAR_LU_biexp(bounds=([0,0,0,0], [1,1,1,1])) +model2 = IAR_LU_biexp() #model2 = IAR_LU_modified_mix() #model2 = OGC_AmsterdamUMC_biexp() diff --git a/tests/IVIMmodels/unit_tests/skip_tests.md b/tests/IVIMmodels/unit_tests/skip_tests.md new file mode 100644 index 00000000..e8bfbf11 --- /dev/null +++ b/tests/IVIMmodels/unit_tests/skip_tests.md @@ -0,0 +1,4 @@ +# For TCML_TechnionIIT_lsqBOBYQA we skip 2 tests: +The pericardium seems to give weird results on some systems. We believe this to be different interpertation of the data and not a systematic error in the algorithm +For the bounds test, on Mac and Ubuntu, TCML_TechnionIIT_lsqBOBYQA returns default values of 0. We do not believe this to be an intrensic error of the code, but an instable performance in small, unrealistic boundaries. +**Consequently, bounds are not tested for TCML_TechnionIIT_lsqBOBYQA and it should be used with caution when bounds are requiered.** \ 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 ded61d36..5b4849d6 100644 --- a/tests/IVIMmodels/unit_tests/test_ivim_fit.py +++ b/tests/IVIMmodels/unit_tests/test_ivim_fit.py @@ -47,9 +47,11 @@ def test_ivim_fit_saved(data_ivim_fit_saved, eng, request, record_property): request.node.add_marker(mark) signal = signal_helper(data["data"]) tolerances = tolerances_helper(tolerances, data) - fit = OsipiBase(algorithm=algorithm, **kwargs) - if fit.use_bounds: - fit.bounds = ([0, 0, 0.005, 0.7], [0.005, 1.0, 0.2, 1.3]) + if data['f'] == 0: + test_bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.01, 0.2], "D" : [0, 0.005]} + else: + test_bounds = {"S0" : [0.7, 1.3], "f" : [0, 1.0], "Dp" : [0.005, 0.2], "D" : [0, 0.005]} + fit = OsipiBase(algorithm=algorithm, bounds=test_bounds, **kwargs) start_time = time.time() # Record the start time fit_result = fit.osipi_fit(signal, bvals) elapsed_time = time.time() - start_time # Calculate elapsed time @@ -68,7 +70,7 @@ def to_list_if_needed(value): "atol": tolerances["atol"] } record_property('test_data', test_result) - if (data['f'] > 0.99 or data['f'] < 0.01) and not fit.use_bounds: #in these cases there are multiple solutions (D can become D*, f will be 0 and D* can be anything. This will be a good description of the data, so technically not a fail + if (data['f'] > 0.99 or data['f'] < 0.01) and not (fit.use_bounds["f"] or fit.use_bounds["D"] or fit.use_bounds["Dp"]): #in these cases there are multiple solutions (D can become D*, f will be 0 and D* can be anything. This will be a good description of the data, so technically not a fail return npt.assert_allclose(fit_result['f'],data['f'], rtol=tolerances["rtol"]["f"], atol=tolerances["atol"]["f"]) if data['f']<0.80: # we need some signal for D to be detected @@ -93,40 +95,47 @@ def test_default_bounds_and_initial_guesses(algorithmlist,eng): 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" - if fit.use_bounds: - assert 0 <= fit.bounds[0][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.bounds[0][0]} is unrealistic" - assert 0 <= fit.bounds[1][0] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.bounds[1][0]} is unrealistic" - assert 0 <= fit.bounds[0][1] <= 1, f"For {algorithm}, the default lower bound of f {fit.bounds[0][1]} is unrealistic" - assert 0 <= fit.bounds[1][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.bounds[1][1]} is unrealistic" - assert 0.003 <= fit.bounds[0][2] <= 0.05, f"For {algorithm}, the default lower bound of Ds {fit.bounds[0][2]} is unrealistic" - assert 0.003 <= fit.bounds[1][2] <= 0.5, f"For {algorithm}, the default upper bound of Ds {fit.bounds[1][2]} is unrealistic" - assert 0 <= fit.bounds[0][3] <= 1, f"For {algorithm}, the default lower bound of S {fit.bounds[0][3]} is unrealistic; note data is normaized" - assert 1 <= fit.bounds[1][3] <= 1000, f"For {algorithm}, the default upper bound of S {fit.bounds[1][3]} is unrealistic; note data is normaized" - assert fit.bounds[1][0] <= fit.bounds[0][2], f"For {algorithm}, the default upper bound of D {fit.bounds[1][0]} is higher than lower bound of D* {fit.bounds[0][2]}" - if fit.use_initial_guess: - assert 0.0008 <= fit.initial_guess[0] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.initial_guess[0]} is unrealistic" - assert 0 <= fit.initial_guess[1] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.initial_guess[1]} is unrealistic" - assert 0.003 <= fit.initial_guess[2] <= 0.1, f"For {algorithm}, the default initial guess for Ds {fit.initial_guess[2]} is unrealistic" - assert 0.9 <= fit.initial_guess[3] <= 1.1, f"For {algorithm}, the default initial guess for S {fit.initial_guess[3]} is unrealistic; note signal is normalized" + if (fit.use_bounds["f"] or fit.use_bounds["D"] or fit.use_bounds["Dp"]): + assert 0 <= fit.osipi_bounds["D"][0] <= 0.003, f"For {algorithm}, the default lower bound of D {fit.osipi_bounds['D'][0]} is unrealistic" + assert 0 <= fit.osipi_bounds["D"][1] <= 0.01, f"For {algorithm}, the default upper bound of D {fit.osipi_bounds['D'][1]} is unrealistic" + assert 0 <= fit.osipi_bounds["f"][0] <= 1, f"For {algorithm}, the default lower bound of f {fit.osipi_bounds['f'][0]} is unrealistic" + assert 0 <= fit.osipi_bounds["f"][1] <= 1, f"For {algorithm}, the default upper bound of f {fit.osipi_bounds['f'][1]} is unrealistic" + assert 0.003 <= fit.osipi_bounds["Dp"][0] <= 0.05, f"For {algorithm}, the default lower bound of Dp {fit.osipi_bounds['Dp'][0]} is unrealistic" + assert 0.003 <= fit.osipi_bounds["Dp"][1] <= 0.5, f"For {algorithm}, the default upper bound of Dp {fit.osipi_bounds['Dp'][1]} is unrealistic" + assert 0 <= fit.osipi_bounds["S0"][0] <= 1, f"For {algorithm}, the default lower bound of S0 {fit.osipi_bounds['S0'][0]} is unrealistic; note data is normaized" + assert 1 <= fit.osipi_bounds["S0"][1] <= 1000, f"For {algorithm}, the default upper bound of S0 {fit.osipi_bounds['S0'][1]} is unrealistic; note data is normaized" + assert fit.osipi_bounds["D"][1] <= fit.osipi_bounds["Dp"][0], f"For {algorithm}, the default upper bound of D {fit.osipi_bounds['D'][1]} is higher than lower bound of Dp {fit.osipi_bounds['Dp'][0]}" + if (fit.use_initial_guess["f"] or fit.use_initial_guess["D"] or fit.use_initial_guess["Dp"]): + assert 0.0008 <= fit.osipi_initial_guess["D"] <= 0.002, f"For {algorithm}, the default initial guess for D {fit.osipi_initial_guess['D']} is unrealistic" + assert 0 <= fit.osipi_initial_guess["f"] <= 0.5, f"For {algorithm}, the default initial guess for f {fit.osipi_initial_guess['f']} is unrealistic" + assert 0.003 <= fit.osipi_initial_guess["Dp"] <= 0.1, f"For {algorithm}, the default initial guess for Dp {fit.osipi_initial_guess['Dp']} is unrealistic" + assert 0.9 <= fit.osipi_initial_guess["S0"] <= 1.1, f"For {algorithm}, the default initial guess for S0 {fit.osipi_initial_guess['S0']} is unrealistic; note signal is normalized" -def test_bounds(bound_input, eng): +def test_bounds(bound_input, eng, request): name, bvals, data, algorithm, xfail, kwargs, tolerances, requires_matlab = bound_input + if xfail["xfail"]: + mark = pytest.mark.xfail(reason="xfail", strict=xfail["strict"]) + request.node.add_marker(mark) if requires_matlab: if eng is None: pytest.skip(reason="Running without matlab; if Matlab is available please run pytest --withmatlab") else: kwargs = {**kwargs, 'eng': eng} - bounds = ([0.0008, 0.2, 0.01, 1.1], [0.0012, 0.3, 0.02, 1.3]) + #bounds = ([0.0008, 0.2, 0.01, 1.1], [0.0012, 0.3, 0.02, 1.3]) + bounds = {"S0" : [1.1, 1.3], "f" : [0.2, 0.3], "Dp" : [0.01, 0.02], "D" : [0.0008, 0.0012]} # deliberately have silly bounds to see whether they are used - fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess = [0.001, 0.25, 0.015, 1.2], **kwargs) - if fit.use_bounds: + fit = OsipiBase(algorithm=algorithm, bounds=bounds, initial_guess={"S0" : 1.2, "f" : 0.25, "Dp" : 0.015, "D" : 0.001}, **kwargs) + if fit.use_bounds["f"] or fit.use_bounds["D"] or fit.use_bounds["Dp"]: signal = signal_helper(data["data"]) fit_result = fit.osipi_fit(signal, bvals) - - assert bounds[0][0] <= fit_result['D'] <= bounds[1][0], f"Result {fit_result['D']} out of bounds for data: {name}" - assert bounds[0][1] <= fit_result['f'] <= bounds[1][1], f"Result {fit_result['f']} out of bounds for data: {name}" - assert bounds[0][2] <= fit_result['Dp'] <= bounds[1][2], f"Result {fit_result['Dp']} out of bounds for data: {name}" + eps=1e-10 # without this margin it can cause floating point failures on mac systems + if fit.use_bounds["D"]: + assert bounds["D"][0]-eps <= fit_result['D'] <= bounds["D"][1]+eps, f"Result {fit_result['D']} out of bounds for data: {name}" + if fit.use_bounds["f"]: + assert bounds["f"][0]-eps <= fit_result['f'] <= bounds["f"][1]+eps, f"Result {fit_result['f']} out of bounds for data: {name}" + if fit.use_bounds["Dp"]: + assert bounds["Dp"][0]-eps <= fit_result['Dp'] <= bounds["Dp"][1]+eps, f"Result {fit_result['Dp']} out of bounds for data: {name}" # S0 is not returned as argument... #assert bounds[0][3] <= fit_result['S0'] <= bounds[1][3], f"Result {fit_result['S0']} out of bounds for data: {name}" '''if fit.use_initial_guess: @@ -249,12 +258,10 @@ def test_deep_learning_algorithms(deep_learning_algorithms, record_property): kwargs = {**kwargs, 'eng': eng} tolerances = tolerances_helper(tolerances, data) - fit = OsipiBase(bvalues=bvals, algorithm=algorithm, **kwargs) + fit = OsipiBase(bvalues=bvals, algorithm=algorithm, bounds={"S0" : [0, 2], "f" : [0, 1], "Dp" : [0.005, 0.2], "D" : [0, 0.005]}, **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 diff --git a/tests/IVIMpreproc/unit_tests/test_degibbs.py b/tests/IVIMpreproc/unit_tests/wip_degibbs.py similarity index 100% rename from tests/IVIMpreproc/unit_tests/test_degibbs.py rename to tests/IVIMpreproc/unit_tests/wip_degibbs.py diff --git a/tests/IVIMpreproc/unit_tests/test_denoise.py b/tests/IVIMpreproc/unit_tests/wip_denoise.py similarity index 96% rename from tests/IVIMpreproc/unit_tests/test_denoise.py rename to tests/IVIMpreproc/unit_tests/wip_denoise.py index bbf26197..d8495b2a 100644 --- a/tests/IVIMpreproc/unit_tests/test_denoise.py +++ b/tests/IVIMpreproc/unit_tests/wip_denoise.py @@ -10,7 +10,7 @@ from phantoms.brain.sim_brain_phantom_preproc import simulate_brain_phantom import nibabel as nib import matplotlib.pyplot as plt -from src.original.EP_GU.brain_pipeline import denoise_wrap +from src.original.preprocessing.EP_GU.brain_pipeline import denoise_wrap def test_denoise_wrap():