Metadata-Version: 2.4
Name: LCS-Retrieval
Version: 0.1.1
Summary: LCS and MIST frameworks for single-exposure multi-contrast X-ray imaging: transmission, phase, isotropic and directional dark-field retrieval
Project-URL: Homepage, https://github.com/MuguiwaraSamy/LCS-Retrieval
Project-URL: Repository, https://github.com/MuguiwaraSamy/LCS-Retrieval
Project-URL: Issues, https://github.com/MuguiwaraSamy/LCS-Retrieval/issues
Author: Samy KEFS
License-Expression: MIT
License-File: LICENSE
Keywords: LCS,MIST,dark-field,imaging,phase-contrast,retrieval,speckle-based,x-ray
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.9
Requires-Dist: numpy>=2.0
Requires-Dist: scipy>=1.10
Provides-Extra: examples
Requires-Dist: fabio>=0.17; extra == 'examples'
Requires-Dist: matplotlib>=3.5; extra == 'examples'
Description-Content-Type: text/markdown

# 🔬 LCS-Retrieval — Low Coherence System & MIST framework for multi-contrast X-ray imaging

[![PyPI version](https://img.shields.io/pypi/v/LCS-Retrieval.svg?color=3776AB&label=PyPI&logo=pypi)](https://pypi.org/project/LCS-Retrieval/)
[![Python versions](https://img.shields.io/pypi/pyversions/LCS-Retrieval.svg?logo=python&logoColor=white)](https://pypi.org/project/LCS-Retrieval/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Status](https://img.shields.io/badge/status-active-success.svg)](https://github.com/MuguiwaraSamy/LCS-Retrieval)

---

**LCS-Retrieval** implements two complementary frameworks for simultaneous retrieval of **transmission**, **phase**, **isotropic dark-field**, and **directional dark-field** from X-ray images:

- **LCS** (Low Coherence System) — implicit sliding-window least-squares solver based on the TIE + Fokker–Planck model ✅ validated
- **MIST** (Multimodal Intrinsic Speckle Tracking) — phase and dark-field retrieval via local contrast statistics and FFT-based inversion ⚠️ implemented, experimental validation ongoing

Both frameworks share a **unified orientation analysis pipeline** for the directional scattering tensor (theta, anisotropy, HSV visualization).

Compatible with **speckle-based**, **modulation-based**, and **single-grid** X-ray imaging setups.

---

## 🚀 Overview

Conventional multi-contrast X-ray imaging requires multiple exposures or phase steps to separate absorption, phase, and scattering signals.
**LCS-Retrieval** eliminates this need by reformulating the inverse problem locally within a sliding window around each pixel.

From a single pair of images (reference + sample), both methods retrieve:

| Signal | LCS symbol | MIST symbol | Description |
|--------|-----------|-------------|-------------|
| 🔲 Transmission | T | Iob | Absorption contrast |
| 🌊 Phase | dy, dx | phi | Phase / wavefront |
| 🌀 Isotropic dark-field | DF | Deff | Overall scattering strength |
| 🧭 Directional dark-field | Dxx, Dxy, Dyy | Dxx, Dxy, Dyy | Anisotropic scattering tensor |

---

## ✳️ Key Features

- 🔹 **Single-exposure** retrieval — no phase stepping or mask motion required
- 🔹 **LCS solver** — sliding-window least-squares on the TIE + Fokker–Planck model ✅
- 🔹 **MIST solver** — local contrast statistics + FFT-based Fokker–Planck inversion ⚠️ (experimental validation in progress)
- 🔹 **Shared orientation pipeline** — `lcs.hsv_retrieval()` and `lcs.ddf_metrics()` work on both LCS and MIST outputs
- 🔹 **Multi-exposure mode** supported for both methods (`win_size=1`)
- 🔹 **External absorption injection** — provide an independent T map for both LCS and MIST
- 🔹 **HSV orientation visualization** of the directional scattering tensor
- 🔹 Compatible with **speckle-based**, **modulation-based**, and **single-grid** setups

---

## ⚙️ Installation

### ▶️ From PyPI (recommended)

```bash
pip install LCS-Retrieval
```

To also install the dependencies for the example notebooks (matplotlib, fabio):

```bash
pip install LCS-Retrieval[examples]
```

### ▶️ From source

```bash
git clone https://github.com/MuguiwaraSamy/LCS-Retrieval.git
cd LCS-Retrieval
pip install -e .
```

**Requirements:** Python ≥ 3.9, NumPy ≥ 2.0, SciPy ≥ 1.10

---

## 🧠 Quick start — LCS

```python
import lcs

# ref, sample: 2D arrays (H, W) or multi-exposure stacks (H, W, N)

# ── Isotropic dark-field ────────────────────────────────────────
result_df = lcs.lcs_df(ref, sample, win_size=5, alpha=1e-5)
# Output shape: (H', W', 4)
T  = result_df[..., 0]   # Transmission
dy = result_df[..., 1]   # Vertical refraction
dx = result_df[..., 2]   # Horizontal refraction
DF = result_df[..., 3]   # Isotropic dark-field

# ── Directional dark-field ──────────────────────────────────────
result_ddf = lcs.lcs_ddf(ref, sample, win_size=5, alpha=1e5)
# Output shape: (H', W', 6) → [T, dy, dx, Dxx, Dxy, Dyy]

# ── HSV orientation visualization ──────────────────────────────
rgb, metrics = lcs.hsv_retrieval(result_ddf, sigma=5.0)
```

---

## 🧠 Quick start — MIST

> [!WARNING]
> The MIST module is **fully implemented** and functional, but has **not yet been experimentally validated**.
> The code is based on the Fokker–Planck + local contrast statistics formulation, but results have not been systematically compared against ground-truth data.
> Use with caution and verify outputs on your own datasets. Validation is ongoing.

```python
import mist
import lcs  # shared orientation pipeline

energy     = 8                    # keV
wavelength = 12.398e-10 / energy  # X-ray wavelength in metres
dist_det   = 0.8                  # metres

# ── Isotropic dark-field + phase ────────────────────────────────
result_df = mist.mist_df(ref, sample, dist_det=dist_det, wavelength=wavelength, win_size=3)
phi  = result_df['phi']   # Phase map
Deff = result_df['Deff']  # Isotropic dark-field

# ── Directional dark-field ──────────────────────────────────────
result_ddf = mist.mist_ddf(
    ref, sample, gamma_mat=100, dist_det=dist_det, wavelength=wavelength, win_size=3
)
# Output shape: (H', W', 6) → [thickness, Iob, G, Dxx, Dxy, Dyy]
thickness = result_ddf[..., 0]
Iob       = result_ddf[..., 1]
G         = result_ddf[..., 2]

# ── Shared orientation pipeline ─────────────────────────────────
rgb, metrics = lcs.hsv_retrieval(result_ddf, sigma=5.0)  # works on MIST too!
```

---

## 🗂️ Acquisition modes

Both methods support the same three modes:

| Mode | Input shape | `win_size` |
|------|-------------|------------|
| Single exposure + sliding window | `(H, W)` | odd integer ≥ 3 |
| Multi-exposure (standard) | `(H, W, N)` | `1` |
| Multi-exposure + sliding window | `(H, W, N)` | odd integer ≥ 3 |

---

## 🧪 External absorption image

Both methods accept an optional `absorption` argument.
The solver divides `sample` by `absorption` before retrieval, and returns the provided map (cropped to the valid window region) in the T/Iob channel.

```python
# T_abs: independently measured absorption image, shape (H, W)

# LCS
result_df  = lcs.lcs_df( ref, sample, win_size=5, absorption=T_abs)
result_ddf = lcs.lcs_ddf(ref, sample, win_size=5, absorption=T_abs)

# MIST
result_df  = mist.mist_df( ref, sample, dist_det=dist_det, wavelength=wavelength, absorption=T_abs)
result_ddf = mist.mist_ddf(ref, sample, gamma_mat=100, dist_det=dist_det,
                            wavelength=wavelength, absorption=T_abs)
```

---

## 🔗 Shared orientation pipeline

Both `lcs.lcs_ddf` and `mist.mist_ddf` return a `(H', W', 6)` stack whose **last 3 channels are always the scattering tensor** `[Dxx, Dxy, Dyy]`. This means the orientation analysis functions work identically on both:

```python
# Works with both lcs.lcs_ddf() and mist.mist_ddf() outputs
rgb, metrics = lcs.hsv_retrieval(result_ddf, sigma=5.0)
metrics      = lcs.ddf_metrics(result_ddf, sigma=5.0)

# Or directly on tensor components
metrics = lcs.tensor_metrics(Dxx, Dxy, Dyy, sigma=5.0)
```

`metrics` contains: `theta`, `theta_smooth`, `coherence`, `aniso`, `traceS`, `major`, `minor`, `lambda_max`, `lambda_min`, `intensity`

---

## 📖 API reference

### LCS

#### `lcs.lcs_df(ref, sample, *, win_size=5, alpha=1e-5, absorption=None)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `ref` | ndarray `(H,W)` or `(H,W,N)` | — | Reference image(s) |
| `sample` | ndarray `(H,W)` or `(H,W,N)` | — | Sample image(s) |
| `win_size` | int | `5` | Sliding window size (odd) |
| `alpha` | float | `1e-5` | Tikhonov regularization |
| `absorption` | ndarray `(H,W)` or `None` | `None` | External absorption image |

Returns `ndarray (H', W', 4)` → `[T, dy, dx, DF]`

#### `lcs.lcs_ddf(ref, sample, *, win_size=5, alpha=1e5, absorption=None)`

Returns `ndarray (H', W', 6)` → `[T, dy, dx, Dxx, Dxy, Dyy]`

---

### MIST ⚠️ experimental

> [!NOTE]
> MIST functions are available in the package but experimental validation is still ongoing.

#### `mist.mist_df(ref, sample, dist_det, wavelength, *, win_size=3, sig_scale=0.0, median_filter_size=0, alpha=1e5, absorption=None)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `ref` | ndarray `(H,W)` or `(H,W,N)` | — | Reference image(s) |
| `sample` | ndarray `(H,W)` or `(H,W,N)` | — | Sample image(s) |
| `dist_det` | float | — | Sample-to-detector distance (m) |
| `wavelength` | float | — | X-ray wavelength in metres (`12.398e-10 / energy_keV`) |
| `win_size` | int | `3` | Sliding window size (odd) |
| `sig_scale` | float | `0.0` | Gaussian high-pass filter scale |
| `median_filter_size` | int | `0` | Median filter on Deff (0 = off) |
| `alpha` | float | `1e5` | Tikhonov regularization |
| `absorption` | ndarray `(H,W)` or `None` | `None` | External absorption image |

Returns `dict` → `{'phi', 'Deff', 'phi_laplacian'}`

#### `mist.mist_ddf(ref, sample, gamma_mat, dist_det, wavelength, *, win_size=3, sig_scale=0.0, alpha=1e5, absorption=None)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `gamma_mat` | float | — | Material delta/beta ratio |
| `dist_det` | float | — | Sample-to-detector distance (m) |
| `wavelength` | float | — | X-ray wavelength in metres (`12.398e-10 / energy_keV`) |
| `sig_scale` | float | `0.0` | Gaussian high-pass filter scale |
| `absorption` | ndarray `(H,W)` or `None` | `None` | External absorption image |

Returns `ndarray (H', W', 6)` → `[thickness, Iob, G, Dxx, Dxy, Dyy]`

---

### Shared orientation functions

#### `lcs.hsv_retrieval(result_ddf, *, sigma=5.0, percentile=99.0, sat_min=0.0, sat_max=1.0, val_min=0.0, val_max=1.0)`

Returns `(rgb, metrics)` — works on both LCS and MIST outputs.

#### `lcs.ddf_metrics(result_ddf, *, sigma=5.0)`

Returns `metrics` dict — works on both LCS and MIST outputs.

#### `lcs.tensor_metrics(Dxx, Dxy, Dyy, *, sigma=5.0)`

Returns `metrics` dict directly from tensor components.

---

## 📘 Example notebooks

| Notebook | Description |
|----------|-------------|
| `examples/example_lcs.ipynb` | LCS isotropic & directional dark-field |
| `examples/example_mist.ipynb` | MIST phase, isotropic & directional dark-field |

---

## 🖥️ ImageJ Plugin

A dedicated **ImageJ/Fiji plugin** for LCS retrieval is also available:
👉 [github.com/MuguiwaraSamy/LCS-ImageJ](https://github.com/MuguiwaraSamy/LCS_Retrieval_IJ)

---

## 📄 Citation

If you use this package in your work, please cite:

```bibtex
@article{kefs2025lcs,
  title   = {Implicit Single-Exposure Retrieval of {X}-ray Phase, Absorption, and Anisotropic Dark-Field},
  author  = {Kefs, Samy and Ninham, Chris and Magnin, Clara and
             Rolland du Roscoat, Sabine and Lhuissier, Pierre and Brun, Emmanuel},
  journal = {IEEE Transactions on Medical Imaging},
  year    = {2025},
}
```

---

## 📜 License

Distributed under the MIT License.
© 2025 Samy KEFS. All rights reserved.
