Skip to content

How to Choose a Population AIF

Select the appropriate population-based arterial input function for DCE-MRI analysis.

Via CLI (YAML Config)

Set population_aif in your pipeline config:

modality: dce
pipeline:
  model: extended_tofts
  aif_source: population
  population_aif: parker  # or georgiou, fritz_hansen, weinmann, mcgrath

Available values: parker, georgiou, fritz_hansen, weinmann, mcgrath. See the characteristics table below to choose.

Via Python API

osipy provides five population AIF models:

import osipy
import numpy as np

time = np.linspace(0, 300, 100)  # seconds

# Parker AIF (most widely used)
parker = osipy.ParkerAIF()(time)

# Georgiou AIF (broader peak)
georgiou = osipy.GeorgiouAIF()(time)

# Fritz-Hansen AIF (earlier model)
fritz_hansen = osipy.FritzHansenAIF()(time)

# Weinmann AIF (classic bi-exponential)
weinmann = osipy.get_population_aif("weinmann")(time)

# McGrath AIF (preclinical, small animal)
mcgrath = osipy.get_population_aif("mcgrath")(time)

Compare AIF Shapes

Visualize the differences between population AIFs:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(10, 5))

ax.plot(time, parker.concentration, 'r-', linewidth=2, label='Parker')
ax.plot(time, georgiou.concentration, 'b-', linewidth=2, label='Georgiou')
ax.plot(time, fritz_hansen.concentration, 'g-', linewidth=2, label='Fritz-Hansen')

ax.set_xlabel('Time (s)')
ax.set_ylabel('Concentration (mM)')
ax.set_title('Population AIF Comparison')
ax.legend()
ax.grid(True, alpha=0.3)
plt.show()

AIF Characteristics

Model Shape Best For
Parker Dual Gaussian + exponential tail Standard DCE, high temporal resolution
Georgiou Dual Gaussian + exponential decay Lower temporal resolution, smoother
Fritz-Hansen Bi-exponential decay Cardiac perfusion, early studies
Weinmann Bi-exponential decay Classic reference, Gd-DTPA pharmacokinetics
McGrath Gamma-variate + exponential washout Preclinical (small animal) studies

Selection Guidelines

All five AIFs at a glance:

# Default for most applications
aif = osipy.ParkerAIF()(time)

# For slower acquisitions (TR > 5s)
aif = osipy.GeorgiouAIF()(time)

# For cardiac perfusion or historical comparison
aif = osipy.FritzHansenAIF()(time)

# Classic Gd-DTPA reference
aif = osipy.get_population_aif("weinmann")(time)

# Preclinical (small animal) studies
aif = osipy.get_population_aif("mcgrath")(time)

Use Parker AIF When:

  • You have standard DCE acquisition (TR < 5s)
  • Measuring tumors, muscle, or general tissues
  • This is your first analysis (most validated)

Use Georgiou AIF When:

  • Lower temporal resolution (TR > 5s)
  • Want smoother concentration curves
  • Bolus injection was slow

Use Fritz-Hansen AIF When:

  • Cardiac perfusion studies
  • Comparing with older literature

Use Weinmann AIF When:

  • Reproducing classic Gd-DTPA pharmacokinetic studies
  • Comparing with Weinmann et al. (1984) reference data

Use McGrath AIF When:

  • Preclinical (small animal) DCE-MRI studies
  • Rodent models where human population AIFs are inappropriate
  • Need a gamma-variate + exponential AIF model

Scale AIF for Injection Dose

Adjust AIF for different contrast agent doses:

from osipy.common.types import AIFType

# Standard dose is 0.1 mmol/kg
# For half dose (0.05 mmol/kg):
dose_factor = 0.05 / 0.1

aif = osipy.ParkerAIF()(time)
aif_scaled = osipy.ArterialInputFunction(
    time=time,
    concentration=aif.concentration * dose_factor,
    aif_type=AIFType.MEASURED,
)

Adjust for Hematocrit

Account for blood hematocrit:

from osipy.common.types import AIFType

def adjust_aif_hematocrit(aif, hct=0.45):
    """Adjust AIF for hematocrit.

    Contrast agent distributes in plasma, not whole blood.
    Standard AIF assumes Hct = 0.45.
    """
    # Plasma fraction
    plasma_fraction = 1 - hct
    standard_plasma = 1 - 0.45

    # Scale concentration
    scale = standard_plasma / plasma_fraction
    adjusted_conc = aif.concentration * scale

    return osipy.ArterialInputFunction(
        time=aif.time,
        concentration=adjusted_conc,
        aif_type=AIFType.MEASURED,
    )

# For patient with Hct = 0.35
aif_adjusted = adjust_aif_hematocrit(parker, hct=0.35)

Compare Fitting Results

Evaluate which AIF works best for your data:

import osipy

# Try different AIFs
aifs = {
    'Parker': osipy.ParkerAIF()(time),
    'Georgiou': osipy.GeorgiouAIF()(time),
}

results = {}
for name, aif in aifs.items():
    result = osipy.fit_model(
        "extended_tofts",
        concentration=concentration,
        aif=aif,
        time=time,
        mask=mask
    )
    results[name] = result

# Compare R² values
for name, result in results.items():
    valid = result.quality_mask > 0
    mean_r2 = result.r_squared_map[valid].mean()
    print(f"{name} AIF: Mean R² = {mean_r2:.4f}")

AIF Properties

Access AIF characteristics:

aif = osipy.ParkerAIF()(time)

# Peak information
print(f"Peak concentration: {aif.peak_concentration:.2f} mM")
print(f"Peak time: {aif.peak_time:.1f} s")

# Access raw data
print(f"Time points: {len(aif.time)}")
print(f"Concentration array: {aif.concentration.shape}")

When to Use Population vs Measured AIF

Scenario Recommendation
No visible artery in FOV Population AIF
Large artery available Measured AIF
Comparing across subjects Population AIF (consistent)
Absolute quantification Measured AIF (more accurate)
Rapid screening Population AIF
Research publication Consider both, report which

Common Issues

AIF Peak Too Early/Late

Shift AIF using the shift_aif utility:

from osipy.common.aif import shift_aif

# Shift AIF by a known delay (in seconds)
aif = osipy.ParkerAIF()(time)
shifted_conc = shift_aif(aif.concentration, time, delay=10.0, xp=np)

Alternatively, use fit_delay=True to estimate the delay automatically during model fitting:

result = osipy.fit_model(
    "extended_tofts", concentration, aif, time,
    fit_delay=True  # Estimates per-voxel delay
)
delay_map = result.parameter_maps["delay"].values  # seconds

Mismatch with Measured Data

Scale AIF to match observed peak concentration:

# If population AIF doesn't match your observed curves,
# consider:
# 1. Different injection rate
# 2. Cardiac output differences
# 3. Acquisition timing issues

# Scale to match observed peak
observed_peak = 4.0  # mM
scale = observed_peak / aif.peak_concentration
aif_scaled = osipy.ArterialInputFunction(
    time=aif.time,
    concentration=aif.concentration * scale,
    aif_type=AIFType.MEASURED,
)

See Also