Skip to content

feat: Add body-part aware initial guesses for IVIM fitting (Feature #87)#149

Open
Devguru-codes wants to merge 1 commit intoOSIPI:mainfrom
Devguru-codes:feature-body-part-initial-guesses
Open

feat: Add body-part aware initial guesses for IVIM fitting (Feature #87)#149
Devguru-codes wants to merge 1 commit intoOSIPI:mainfrom
Devguru-codes:feature-body-part-initial-guesses

Conversation

@Devguru-codes
Copy link
Contributor

@Devguru-codes Devguru-codes commented Mar 1, 2026

Hello @etpeterson - I saw no updates were made on this issue so I worked on it. If there is need of improvement, comment it and i will update this pr.

Description

Closes #87. Adds organ-specific initial guesses, bounds, and thresholds for IVIM fitting, sourced from peer-reviewed literature. This enables faster convergence, reduced local minima, and more physiologically plausible results when scanning specific body parts.

Problem

The current generic defaults (f=0.1, D=0.001, Dp=0.01) are reasonable for the brain but significantly off for other organs:

Parameter Current Generic Brain (actual) Liver (actual) Kidney (actual)
f 0.10 0.03–0.06 0.10–0.15 0.18–0.25
D (×10⁻³) 1.00 0.70–0.80 0.96–1.19 1.78–1.89
Dp (×10⁻³) 10.0 7–25 40–80 ❌ 6× off 25–30 ❌ 3× off

Solution: Literature-Sourced Lookup Table

Body Part f D (×10⁻³) Dp (×10⁻³) Source
Brain 0.05 0.80 10.0 Federau 2017 (DOI)
Liver 0.12 1.00 60.0 Dyvorne 2013 (DOI), Guiu 2012 (DOI)
Kidney 0.20 1.90 30.0 Li 2017 (DOI), Ljimani 2020 (DOI)
Prostate 0.08 1.50 25.0 Kuru 2014 (DOI)
Pancreas 0.18 1.20 20.0 Barbieri 2020 (DOI)
Head & Neck 0.15 1.00 25.0 Sumi 2012 (DOI)
Breast 0.10 1.40 20.0 Lee 2018 (DOI)
Placenta 0.28 1.70 40.0 Zhu 2023 (DOI)

Each body part also includes organ-specific bounds (tighter than the generic ones) to constrain the optimizer to physiologically plausible regions.

Justification for chosen values:

  • Brain: Federau 2017 reports f = 0.03–0.06, D ≈ 0.8, D* ≈ 7–25. Our f=0.05, D=0.8, Dp=10.0 is perfectly centered.
  • Liver: Dyvorne 2013/Guiu 2012 report f = 13–25%, D ≈ 1.2, D* ≈ 55–82. Our f=0.12, D=1.0, Dp=60.0 is standard for liver.
  • Kidney: Ljimani 2020 (consensus paper) reports Cortex D=1.89, f=20.7%. Li 2017 reports D* ≈ 28. Our f=0.20, D=1.90, Dp=30.0 matches the cortex.
  • Prostate: Kuru 2014 reports f=8.6%, D=1.46, D*=28.7. Our f=0.08, D=1.50, Dp=25.0 is excellent.
  • Breast: Lee 2018 confirms f ≈ 10%, D ≈ 1.40. Our Dp=20.0 is standard for fibroglandular tissue.
  • Placenta: Zhu 2023 indicates f ≈ 28% and D* ≈ 40. Our values reflect this exactly.

Changes Made

Action File Description
NEW src/wrappers/ivim_body_part_defaults.py Lookup table with 8 body parts + generic, get_body_part_defaults(), get_available_body_parts()
MODIFY src/wrappers/OsipiBase.py Add body_part param to __init__(), support initial_guess as string
NEW tests/.../test_body_part_defaults.py 22 unit tests

API Usage

# New: body_part parameter (sets both initial_guess AND bounds)
fit = OsipiBase(algorithm="IAR_LU_biexp", bvalues=bvals, body_part="liver")

# New: string-based initial_guess (as suggested by maintainer)
fit = OsipiBase(algorithm="IAR_LU_biexp", bvalues=bvals, initial_guess="brain")

# User overrides always win
fit = OsipiBase(algorithm="IAR_LU_biexp", bvalues=bvals,
                body_part="liver", initial_guess=custom_dict)
# → custom_dict is used for initial guess, liver bounds still applied

# 100% backward compatible — default behavior unchanged
fit = OsipiBase(algorithm="IAR_LU_biexp", bvalues=bvals)

Testing

  • 22 new unit tests: all pass — covers lookup correctness, case insensitivity, error handling, user override priority, string-based initial_guess, data integrity (all guesses within bounds)
  • Full regression suite: 1127 passed, 167 skipped, 22 xfailed, 6 xpassed — identical to main baseline
  • End-to-end fitting test with body_part="liver" produced near-perfect results:
    True:   f=0.120  D=0.0010  Dp=0.060
    Fitted: f=0.120  D=0.0010  Dp=0.060
    
  • Zero regressions

Known Issue (Pre-existing, Not Introduced by This PR)

When using body_part= together with algorithm="IAR_LU_biexp", the IAR_LU_biexp algorithm's internal set_bounds() crashes with KeyError: 0 because it expects bounds as list-of-lists (bounds[0], bounds[1]) but receives dict-format bounds. This is the same bug on main that is already documented in Issue #86 and fixed in PR #142 — it reproduces identically with body_part=None (generic defaults). Once PR #142 is merged, this will work seamlessly.

Backward Compatibility

  • body_part=None (default) → identical to current behavior
  • initial_guess=dict → identical to current behavior
  • User-provided bounds/initial_guess always override body-part defaults
  • No existing API signatures changed

Checklist

  • Self-review of changed code
  • Added automated tests — 22 unit tests in tests/IVIMmodels/unit_tests/test_body_part_defaults.py
  • 100% backward compatible — no existing API signatures changed
  • Literature-sourced values with DOI references for all 8 body parts
  • No new dependencies (numpy, scipy already required)
  • Tested against full regression suite — zero regressions

…SIPI#87)

Add literature-sourced default initial guesses, bounds, and thresholds
for 8 anatomical regions: brain, liver, kidney, prostate, pancreas,
head_and_neck, breast, placenta.

New files:
- src/wrappers/ivim_body_part_defaults.py: lookup table module with
  get_body_part_defaults() and get_available_body_parts()
- tests/IVIMmodels/unit_tests/test_body_part_defaults.py: 22 unit tests

Modified files:
- src/wrappers/OsipiBase.py: add body_part parameter to __init__(),
  support initial_guess as string (e.g. initial_guess='liver')

API usage:
  OsipiBase(algorithm='X', bvalues=b, body_part='liver')
  OsipiBase(algorithm='X', bvalues=b, initial_guess='brain')

100% backward compatible: body_part=None uses original generic defaults.
User-provided bounds/initial_guess always take priority.

No regressions: 1127 passed, 167 skipped, 22 xfailed, 6 xpassed.
Copy link
Collaborator

@oliverchampion oliverchampion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey DevGuru, thanks for implementing this! Some general comments. As some of these implementation also requiere expert input and consensus (bounds, initial guesses) I have asked the community to comment too.

"liver": {
"initial_guess": {"S0": 1.0, "f": 0.12, "Dp": 0.06, "D": 0.001},
"bounds": {
"S0": [0.7, 1.3],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, the S0 bounds are defined by the SNR (i.e. more noisy data will mean S0 varies more). I would be in favour to broaden these bounds (i.e. 0.5-2).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(for every organ)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I made the it tighter as a conservative starting point for all organs. I will update it and broaden it for better accomodation.

"Dp": [0.005, 0.05],
"D": [0.0003, 0.002],
},
"thresholds": [200],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In lieu of the IVIM review paper, a cut-off of 300 is adviced for brain

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all other organs have 200 adviced

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the threshold of 200 based on general practice like this paper Federau 2017, DOI: 10.1002/nbm.3780), which uses b-value cutoffs around 200 s/mm² in their segmented fittings.

"initial_guess": {"S0": 1.0, "f": 0.12, "Dp": 0.06, "D": 0.001},
"bounds": {
"S0": [0.7, 1.3],
"f": [0.0, 0.40],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all organs --> I have asked the IVIM experts to review these bounds :). I personally think they are somewhat tight, but lets see what the verdict is. They should be simple to adapt once we have made up our minds on what they should be

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really appreciate it. Expert's opinion will help here. I will wait to touch this part and update it after expert opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also for expert's reference, all of the bounds that i chose is based of papers and their list is given in this PR.

Comment on lines +134 to +136
raise ValueError(
f"Unknown body part '{body_part}'. "
f"Available body parts: {available}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to also provide the user with a list of the available/implemented options here

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be achieved easily by using the function below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have implemented this in lines 134 to 136 of ivim_body_part_defaults.py file. it has this function get_available_body_parts(). This function lists all available options in the error message. It is like this -

available = sorted(IVIM_BODY_PART_DEFAULTS.keys())
raise ValueError(
    f"Unknown body part '{body_part}'. "
    f"Available body parts: {available}"
)

So the user sees:

>`ValueError: Unknown body part 'elbow'. Available body parts: ['brain', 'breast', 'head_and_neck', 'kidney', 'liver', 'pancreas', 'placenta', 'prostate']`


def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, algorithm=None, force_default_settings=True, **kwargs):
def __init__(self, bvalues=None, thresholds=None, bounds=None, initial_guess=None, algorithm=None, force_default_settings=True, body_part=None, **kwargs):
from src.wrappers.ivim_body_part_defaults import get_body_part_defaults
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this import should be at the top of the file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I wlll do it.


if self.thresholds is None:
self.thresholds = np.array([200])
if body_part is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I'd like to see behaviour doing:

If body_part is not None: initialize with body_part-specific values, but then override any non-None values from user input. So if I use body_part = liver, threshold = 500, it takes the initial guess and bounds from the liver, but overrides the threshold by 500.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. This adds flexibility and user control as well. I update logic so that when body_part is provided it will load body part default values first(initial_guess, bounds, thresholds) and then any non-None user-provided values will selectively override the body-part defaults. I will update this.

@Devguru-codes
Copy link
Contributor Author

I will make all updates at once when the expert's review will come.


References:
Brain: Federau 2017 (DOI: 10.1002/nbm.3780)
Liver: Dyvorne 2013 (DOI: 10.1016/j.ejrad.2013.03.003),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOI is incorrect

References:
Brain: Federau 2017 (DOI: 10.1002/nbm.3780)
Liver: Dyvorne 2013 (DOI: 10.1016/j.ejrad.2013.03.003),
Guiu 2012 (DOI: 10.1002/jmri.23762)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOI is incorrect

Brain: Federau 2017 (DOI: 10.1002/nbm.3780)
Liver: Dyvorne 2013 (DOI: 10.1016/j.ejrad.2013.03.003),
Guiu 2012 (DOI: 10.1002/jmri.23762)
Kidney: Li 2017 (DOI: 10.1002/jmri.25571),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either DOI is incorrect (it does refer to a Li et al. paper from 2016), or the paper is an incorrect source)

Liver: Dyvorne 2013 (DOI: 10.1016/j.ejrad.2013.03.003),
Guiu 2012 (DOI: 10.1002/jmri.23762)
Kidney: Li 2017 (DOI: 10.1002/jmri.25571),
Ljimani 2020 (DOI: 10.1007/s10334-019-00802-0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOI non-existent

Guiu 2012 (DOI: 10.1002/jmri.23762)
Kidney: Li 2017 (DOI: 10.1002/jmri.25571),
Ljimani 2020 (DOI: 10.1007/s10334-019-00802-0)
Prostate: Kuru 2014 (DOI: 10.1007/s00330-014-3165-y)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOI non-existent

Ljimani 2020 (DOI: 10.1007/s10334-019-00802-0)
Prostate: Kuru 2014 (DOI: 10.1007/s00330-014-3165-y)
Pancreas: Barbieri 2020 (DOI: 10.1002/mrm.27910)
Head/Neck: Sumi 2012 (DOI: 10.1259/dmfr/15696758)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOI non-existent

Prostate: Kuru 2014 (DOI: 10.1007/s00330-014-3165-y)
Pancreas: Barbieri 2020 (DOI: 10.1002/mrm.27910)
Head/Neck: Sumi 2012 (DOI: 10.1259/dmfr/15696758)
Breast: Lee 2018 (DOI: 10.1097/RCT.0000000000000661)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refers to: Lei et al., 2018, Intravoxel Incoherent Motion Diffusion-Weighted Imaging Versus Dynamic Contrast-Enhanced Magnetic Resonance Imaging: Comparison of the Diagnostic Performance of Perfusion-Related Parameters in Breast. Is that the one you want to cite here?

Pancreas: Barbieri 2020 (DOI: 10.1002/mrm.27910)
Head/Neck: Sumi 2012 (DOI: 10.1259/dmfr/15696758)
Breast: Lee 2018 (DOI: 10.1097/RCT.0000000000000661)
Placenta: Zhu 2023 (DOI: 10.1002/jmri.28858)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOI incorrect


IVIM_BODY_PART_DEFAULTS = {
"brain": {
"initial_guess": {"S0": 1.0, "f": 0.05, "Dp": 0.01, "D": 0.0008},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these initial guesses based on? Are they representing the expected value for each organ?

If so, I would propose to keep this in line with the work in progress IVIM review paper (“Towards Clinical Translation of Intravoxel Incoherent Motion MRI: Acquisition and Analysis Consensus Recommendations”, JMRI, Sigmund et al.) to prevent a proliferation of expected values, standard deviations and bounds.

I would propose to use these values for the following organs with the specified expected value per parameter
Organ | Review | D (10-3 mm2/s) | f (%) | D* (10-3 mm2/s)
Brain | Vieni 2020 [1] | 0.83 | 7.64 | 10.88
Kidney | Ljimani 2020 [2] | 1.89 | 18.88 | 40.53
Liver | Li 2017 [3] | 1.09 | 23.05 | 70.02
Muscle | Englund 2022 [4] | 1.47 | 10.34 | 30.88
Breast(benign) | Liang 2020 [5] | 1.43 | 7.00 | 52.33
Breast(malignant) | Liang 2020 [5] | 0.97 | 11.31 | 37.76
Pancreas(benign) | Zhu 2021 [6] | 1.41 | 20.03 | 25.39
Pancreas(malignant) | Zhu 2021 [6] | 1.40 | 12.39 | 22.16

References:
[1] Vieni, C., et al., Effect of intravoxel incoherent motion on diffusion parameters in normal brain. Neuroimage, 2020. 204: p. 116228.
[2] Ljimani, A., et al., Consensus-based technical recommendations for clinical translation of renal diffusion-weighted MRI. MAGMA, 2020. 33(1): p. 177-195.
[3] Li, Y.T., et al., Liver intravoxel incoherent motion (IVIM) magnetic resonance imaging: a comprehensive review of published data on normal values and applications for fibrosis and tumor evaluation. Quant Imaging Med Surg, 2017. 7(1): p. 59-78.
[4] Englund, E.K., et al., Intravoxel Incoherent Motion Magnetic Resonance Imaging in Skeletal Muscle: Review and Future Directions. J Magn Reson Imaging, 2022. 55(4): p. 988-1012.
[5] Liang, J., et al., Intravoxel Incoherent Motion Diffusion-Weighted Imaging for Quantitative Differentiation of Breast Tumors: A Meta-Analysis. Front Oncol, 2020. 10: p. 585486.
[6] Zhu, M., et al., Accuracy of quantitative diffusion-weighted imaging for differentiating benign and malignant pancreatic lesions: a systematic review and meta-analysis. European Radiology, 2021. 31(10): p. 7746-7759.

IVIM_BODY_PART_DEFAULTS = {
"brain": {
"initial_guess": {"S0": 1.0, "f": 0.05, "Dp": 0.01, "D": 0.0008},
"bounds": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How the comment above works out for the bounds then I'm not completely sure. However, I suppose it's better to be safe and use 'wide' margins for the bounds. I would opt for bounds wider than they are implemented here. This might be an impactful decision (since it can be used as base for fitting pipelines down the line), does it require some discussion @oliverchampion ?.

If let's say we were to use 4 SDs from the mean value reported in the Sigmund et al. review paper, and clip the values to the original bounds (f: [0, 100], D to [0, 0.005] and D* to [0.005, 0.2]) we would end up with bounds like:

with f in %, D in mm^2/s and D* in mm^2/s
{
"brain": {
"D": [0.00055, 0.00111],
"Dp": [0.005, 0.03],
"f": [0.0, 20.28]
},
"kidney": {
"D": [0.00129, 0.00249],
"Dp": [0.005, 0.11905],
"f": [0.0, 43.80]
},
"liver": {
"D": [0.00041, 0.00177],
"Dp": [0.005, 0.19406],
"f": [0.0, 56.97]
},
"muscle": {
"D": [0.00035, 0.00259],
"Dp": [0.005, 0.18760],
"f": [0.0, 34.02]
},
"breast_benign": {
"D": [0.0, 0.00291],
"Dp": [0.005, 0.16633],
"f": [0.0, 23.88]
},
"breast_malignant": {
"D": [0.0, 0.00225],
"Dp": [0.005, 0.11424],
"f": [0.0, 29.35]
},
"pancreas_benign": {
"D": [0.0, 0.00429],
"Dp": [0.005, 0.08015],
"f": [0.0, 51.99]
},
"pancreas_malignant": {
"D": [0.0, 0.00332],
"Dp": [0.005, 0.07348],
"f": [0.0, 32.27]
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we should have wide bounds. We should be careful as we're often looking for lesions, which means we are looking for values that deviate from healthy tissue (which are the easiest values to find in literature). We cannot have bounds that removes/reduces this desired contrast.

I'd be in favor of being careful and go for physical/theoretical bounds. In the liver bounds that Daan has cooked up with the SD's for example (or many of the examples provided in the PR). Is there really no chance of having diffusivity values between 0.0018 and 0.005? That's currently the forbidden gap between D and D* in bounds that are generated that way. With bounds like these, we're not really allowing the algorithms to interpret the signal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Ivan, just discussed this within TF6.3. We agree with your point of view. Summarizing, we'd propose to:

  • Use wide bounds and be on the safe side
  • Make a distinction in bounds between brain and body (what they will be needs to be determined still)
  • initial guess can be organ-specific (based on the reported healthy tissue means in the IVIM review paper)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @DKuppens and @IvanARashid sir. Thank you for the review. Based on consensus, there is what i am thinking -

  1. Organ List & Initial Guesses: - I will update the lookup table to strictly mirror the expected healthy tissue means from the upcoming Sigmund et al. consensus paper. This includes adding Muscle, splitting Breast and Pancreas into benign/malignant, removing Prostate and Head/Neck, and updating all the DOIs to match the citations you provided.
  2. Bounds (Brain vs. Body): - I will discard the 4-SD calculated bounds in favor of wide, physical bounds to ensure algorithms can freely interpret the signal (especially for lesions).

I have 1 doubt regarding the bounds. @DKuppens sir mentioned making a distinction between brain and body ("what they will be needs to be determined still"). Since the 4-SD bounds calculated above (f: 20%, D: 0.001, etc.) are no longer the plan, I will wait for you to finalize those wide physical limits for Brain vs Body (or I propose a set of wide physical limits for you to review? like - D: [0, 0.005], f: [0, 1] for body.

After this, I will work on updates. Thank you.

@DKuppens

This comment was marked as duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] <Add reasonable initial guesses>

4 participants