Fitting models

If your main question is "how do I use this with my own files?", start with Use your own TAC, AIF, and time data.

Fast path

Most users only need this:

  1. Load TAC, AIF, and time arrays from disk.
  2. Make sure tacs is (T, N) or (T,).
  3. Call fit_compartment_model(...).

High-level API

Use fit_compartment_model(...) when your inputs are NumPy arrays or when you want one entrypoint that handles basic preprocessing.

fit_compartment_model(
    tacs,
    aif,
    time,
    *,
    model="3k_vB",
    vB=None,
    aif_time=None,
    interpolate=True,
    frame_duration=1.0 / 60.0,
    time_unit="min",
    device=None,
    dtype=None,
    reg=1e-6,
    eps=1e-8,
    column_scale=True,
    nnls=False,
    nnls_max_iter=200,
    nnls_tol=1e-6,
)

Inputs

  • tacs: tissue activity curves with shape (T, N) or (T,)
  • aif: arterial input function with shape (T,)
  • time: TAC time vector with shape (T,)
  • aif_time: optional separate time vector for aif

Outputs

The function always returns NumPy arrays:

  • kinetic_params
  • fitted_tacs, shape (T, N) for multiple TACs
  • mse, shape (N,)

Supported model variants

Use one of the following model names:

  • "3k" or "lls_3k": fixed vB, returns [K1, k2, k3, Ki]
  • "3k_vB" or "lls_3k_vB": fits vB, returns [K1, k2, k3, vB, Ki]
  • "4k" or "lls_4k": fixed vB, returns [K1, k2, k3, k4, Ki]
  • "4k_vB" or "lls_4k_vB": fits vB, returns [K1, k2, k3, k4, vB, Ki]

For fixed-vB models, pass vB= explicitly.

Time handling

  • time_unit="min" is the default.
  • If your time vectors are in seconds, set time_unit="s".
  • interpolate=True builds a common internal grid before fitting.
  • frame_duration is interpreted in minutes.
  • If interpolate=False, time and aif_time must match exactly.

Typical example:

import numpy as np
from lls import fit_compartment_model

time_sec = np.linspace(0, 45 * 60, 30, dtype=np.float32)
aif = np.exp(-time_sec / 120.0).astype(np.float32)
tacs = (aif[:, None] * np.linspace(0.5, 1.2, 100)[None, :]).astype(np.float32)

params, fitted, mse = fit_compartment_model(
    tacs=tacs,
    aif=aif,
    time=time_sec,
    model="4k_vB",
    time_unit="s",
    interpolate=True,
    frame_duration=1.0 / 60.0,
)

Fixed-vB fitting:

params, fitted, mse = fit_compartment_model(
    tacs=tacs,
    aif=aif,
    time=time_sec,
    model="3k",
    vB=0.05,
    time_unit="s",
)

Separate TAC and AIF time vectors:

params, fitted, mse = fit_compartment_model(
    tacs=tacs,
    aif=aif,
    time=time_tac_s,
    aif_time=time_aif_s,
    model="3k_vB",
    time_unit="s",
    interpolate=True,
)

Low-level Torch APIs

If you already manage tensors, dtype, device placement, and interpolation yourself, call the solver functions directly:

  • lls_3k(Cpet, Cp, t, vB=...)
  • lls_3k_vB(Cpet, Cp, t)
  • lls_4k(Cpet, Cp, t, vB=...)
  • lls_4k_vB(Cpet, Cp, t)

These functions:

  • require torch.Tensor inputs
  • expect matching dtype and device
  • return Torch tensors
  • do not perform time conversion or interpolation

Example:

import torch
from lls import lls_4k_vB

t = torch.linspace(0.0, 40.0, 30, dtype=torch.float32)
Cp = torch.exp(-t / 10.0)
Cpet = Cp[:, None] * torch.linspace(0.6, 1.1, 64)[None, :]

kinetic_params, fitted_Cpet, mse = lls_4k_vB(
    Cpet=Cpet,
    Cp=Cp,
    t=t,
)

For validation workflows, see Simulation pipeline and Prediction and benchmarking.