![test workflow](https://github.com/c-salomonsen/lls/actions/workflows/tests.yml/badge.svg)

# Linear Least Squares for Dynamic PET

`lls` provides a fast linear least-squares solver for 2-tissue compartment modeling in PyTorch.

The main product is the solver itself:
- a high-level fitting entrypoint: `fit_compartment_model(...)`
- low-level Torch solvers: `lls_3k`, `lls_3k_vB`, `lls_4k`, `lls_4k_vB`

The repository also includes simulation, prediction, and benchmark workflows for validation.

## Documentation

Long-form usage and hosting documentation lives under [`docs/`](docs/) in MkDocs format,
and can also be visited at [lls.salodev.no/](https://lls.salodev.no/).

- [Start here: use your own TAC/AIF/time data](docs/usage/bring-your-own-data.md)
- [Documentation home](docs/index.md)
- [Installation](docs/installation.md)
- [Fitting models](docs/usage/fitting.md)
- [Simulation pipeline](docs/validation/simulation.md)
- [Prediction and benchmarking](docs/validation/evaluation.md)
- [Self-hosting the docs](docs/operations/deployment.md)

## Installation

### Option 1: Install from private index

If you need a specific PyTorch build, install that first. Otherwise the commands below can resolve public dependencies from PyPI.

```sh
pip install torch
# or
uv add torch
```

Then install `lls`:

```sh
pip install --extra-index-url https://pypi.salodev.no/simple/ lls
# or
uv add lls --index salodev=https://pypi.salodev.no/simple/
```

On older macOS Intel systems:

```sh
pip install --extra-index-url https://pypi.salodev.no/simple/ "lls[mac-intel]"
# or
uv add "lls[mac-intel]" --index salodev=https://pypi.salodev.no/simple/
```

Do not use `uv add lls --index-url ...` here. In `uv`, `--index-url` replaces the default index, so dependencies like `numpy` may stop resolving unless your private index mirrors PyPI.

If you also need the simulation and benchmarking utilities, install the optional extra:

```sh
pip install --extra-index-url https://pypi.salodev.no/simple/ "lls[sim]"
# or
uv add "lls[sim]" --index salodev=https://pypi.salodev.no/simple/
```

### Option 2: Install from this repository

```sh
git clone git@github.com:c-salomonsen/LLS.git
cd LLS
uv sync
```

## Quickstart

```python
import numpy as np
from lls import fit_compartment_model

time_min = np.linspace(0, 45, 30, dtype=np.float32)
aif = np.exp(-time_min / 8.0).astype(np.float32)
tacs = (aif[:, None] * np.linspace(0.7, 1.1, 8)[None, :]).astype(np.float32)

params, fitted, mse = fit_compartment_model(
    tacs=tacs,
    aif=aif,
    time=time_min,
    model="4k_vB",
    interpolate=False,
)
```

## Using your own data

`lls` does not require a specific file format. Load your TAC, AIF, and time arrays with whatever reader matches your files, then pass the arrays to `fit_compartment_model(...)`.

- `tacs`: shape `(T, N)` for multiple curves or `(T,)` for one curve
- `aif`: shape `(T,)`
- `time`: shape `(T,)`
- if your TAC matrix is `(N, T)`, transpose it before fitting
- if time is stored in seconds, use `time_unit="s"`

Example with a CSV file:

```python
import numpy as np
import pandas as pd
from lls import fit_compartment_model

df = pd.read_csv("subject01_curves.csv")

time_s = df["time_s"].to_numpy(dtype=np.float32)
aif = df["aif"].to_numpy(dtype=np.float32)
tacs = df[["roi_1", "roi_2", "roi_3"]].to_numpy(dtype=np.float32)

params, fitted, mse = fit_compartment_model(
    tacs=tacs,
    aif=aif,
    time=time_s,
    model="4k_vB",
    time_unit="s",
)
```

For more examples, including separate TAC and AIF time grids, see [`docs/usage/bring-your-own-data.md`](docs/usage/bring-your-own-data.md).

## Developing the docs

```sh
uv sync --group dev
uv run mkdocs serve
```

To build the static site:

```sh
uv run mkdocs build --strict
```

## Running tests

```sh
uv run pytest
```
