Tiresias Usage Guide

Tiresias estimates a blind point-spread function (PSF) from a 3-D TIFF volume with CuPy, then can use that PSF for cuCIM Richardson-Lucy restoration.

The production path requires a CUDA-capable GPU. The SciPy implementation is included as a numerical reference and for tests, not as the intended production backend.

Install

The package metadata targets CUDA 11.x through cupy-cuda11x.

cd tiresias
python -m pip install .

Verify the GPU stack:

python - <<'PY'
import cupy
import cucim

print("cupy", cupy.__version__)
print("cucim", cucim.__version__)
print("gpu_count", cupy.cuda.runtime.getDeviceCount())
PY

If getDeviceCount() raises a CUDA driver/runtime error, fix the host CUDA driver or install a package build that matches the driver before running Tiresias.

Input Expectations

  • Input image volumes are TIFF stacks.

  • Arrays are interpreted as (z, y, x).

  • Pixel sizes are in micrometers.

  • Intensities should be finite and non-negative after background handling.

  • The PSF seed must fit inside the selected blind-estimation volume. If the seed has more Z planes than the blind window, Tiresias center-crops it in Z and renormalizes it.

Tiresias currently excludes MATLAB fallback and OME-Zarr workflow I/O.

Estimate a PSF

Minimal CLI example:

tiresias-estimate-psf \
  --image-path volume.tif \
  --output-path estimated_psf.tif \
  --dxy 0.108 \
  --dz 0.300 \
  --wavelength 0.561 \
  --detection-na 1.0 \
  --ni 1.33 \
  --ns 1.33

Useful tuning flags:

tiresias-estimate-psf \
  --image-path volume.tif \
  --output-path estimated_psf.tif \
  --dxy 0.108 \
  --dz 0.300 \
  --wavelength 0.561 \
  --detection-na 1.0 \
  --ni 1.33 \
  --ns 1.33 \
  --n-iters 20 \
  --chunk-xy 256 \
  --blind-max-tiles 16 \
  --blind-z-slices 128 \
  --cupy-fft-engine scout \
  --pad-xy 32 \
  --pad-z 20 \
  --latent-update-period 2 \
  --vram-gb 24

Common PSF-estimation options:

Option

Default

Meaning

--n-iters

10

Blind Richardson-Lucy iterations per tile.

--chunk-xy

256

Requested XY core tile size. Tiresias may reduce it to fit VRAM.

--blind-max-tiles

16

Maximum representative high-SNR tiles. Use 0 for the full grid.

--blind-z-slices

128

Maximum Z planes used for blind estimation. Use 0 for full Z.

--cupy-fft-engine

scout

scout runs a short pass over selected tiles, keeps the most self-consistent PSFs, then finishes with CuPy. Use cupyx to run every selected tile for the full iteration count.

--adaptive-scout-iters

2

Number of short scout iterations used by --cupy-fft-engine scout.

--adaptive-keep-tiles

4

Number of scout-approved tiles to finish.

--pad-xy

32

XY halo around each tile.

--pad-z

20

Symmetric Z padding inside each CuPy blind-RL tile.

--latent-update-period

2

Update the latent image every N iterations. Use 1 for full alternating updates.

--snr-weight-cap

100

Caps any one tile’s contribution to the merge.

--vram-gb

auto

Override detected free VRAM for tile sizing.

--cache-dir

.psf_cache next to input

Cache directory for merged PSFs.

--no-psf-cache

off

Force recomputation.

The output PSF is a float32 TIFF normalized to sum to one.

Deconvolve a TIFF Volume

tiresias-deconvolve \
  --image-path volume.tif \
  --psf-path estimated_psf.tif \
  --output-path restored.tif \
  --n-iters 20 \
  --device-id 0

The deconvolution command loads the image and PSF from TIFF, runs cucim.skimage.restoration.richardson_lucy with clip=False, and writes a float32 TIFF.

Python API

Estimate a PSF:

from pathlib import Path

from tifffile import imwrite

from tiresias import estimate_psf_from_chunks, generate_theoretical_psf

seed = generate_theoretical_psf(
    detection_na=1.0,
    wavelength=0.561,
    ni=1.33,
    ns=1.33,
    dxy=0.108,
    dz=0.300,
    psf_size_z=61,
    psf_size_xy=128,
)

psf = estimate_psf_from_chunks(
    image_path=Path("volume.tif"),
    psf_seed=seed,
    n_iters=20,
    chunk_xy=256,
    blind_max_tiles=16,
)

imwrite("estimated_psf.tif", psf)

Run cuCIM deconvolution:

from tifffile import imread, imwrite

from tiresias import deconvolve_with_cucim

image = imread("volume.tif")
psf = imread("estimated_psf.tif")
restored = deconvolve_with_cucim(image, psf, n_iters=20, device_id=0)
imwrite("restored.tif", restored)

Use the SciPy reference implementation for small validation cases:

from tiresias import estimate_blind_psf_scipy

estimated_psf = estimate_blind_psf_scipy(observed, initial_psf, n_iters=4)

PSF Seed Modes

The CLI currently generates a single-detection theoretical PSF seed. The Python API also exposes generate_psf_seed() for light-sheet seed construction:

from tiresias import generate_psf_seed

seed = generate_psf_seed(
    psf_mode="light_sheet",
    na=1.0,
    detection_na=1.0,
    illumination_na=0.2,
    wavelength=0.561,
    ni=1.33,
    ns=1.33,
    ni0=None,
    tg=None,
    tg0=None,
    ng=None,
    ng0=None,
    ti0=None,
    oversample_factor=3,
    psf_model="vectorial",
    dxy=0.108,
    dz=0.300,
    psf_size_z=61,
    psf_size_xy=128,
    background=0.0,
    light_sheet_angle=90.0,
)

psf_mode="single" returns the detection seed. psf_mode="light_sheet" multiplies the detection seed by a rotated illumination seed and normalizes the result.

Performance Notes

  • Tiresias clamps blind PSF estimation to one CuPy tile worker. This avoids multiple Python processes competing for one CUDA context and cuFFT workspace.

  • --chunk-xy is a request, not a guarantee. Tiresias estimates cuFFT memory use and lowers the tile size when needed.

  • If CuPy raises an out-of-memory error, Tiresias discards the partial pass and retries every tile with the next smaller aligned tile size.

  • --blind-max-tiles 16 is usually much faster than processing the whole grid. Use --blind-max-tiles 0 for comparison or reproducibility studies where full-grid coverage is required.

  • --cupy-fft-engine scout is the default speed-oriented mode. It is most useful when high-SNR tile selection may include contaminated, saturated, edge-heavy, or otherwise unrepresentative regions; the scout pass filters tiles by PSF shape agreement before spending the remaining iterations.

  • --cupy-fft-engine cupyx is the direct full-iteration path. Use it when you want every selected tile to contribute, or when a dataset may contain real spatial PSF variation that the scout filter could treat as disagreement.

  • Lowering --blind-max-tiles below 16 can increase speed significantly because fewer CuPy blind-RL tiles are run. Treat it as an explicit throughput knob: validate against 16 tiles, MATLAB, or a known-good reference before using lower values as a production default.

  • The cache key includes image metadata, seed content, tile sizing, selected Z window, iteration count, and tile-selection settings.

Troubleshooting

cudaErrorInsufficientDriver

The installed CUDA runtime is newer than the host driver supports, or the driver is not visible inside the job/container. Use nvidia-smi and the CuPy verification snippet above to confirm driver/runtime compatibility.

CuPy is missing from the worker environment

Install Tiresias in an environment that includes the required CuPy wheel. The default package metadata installs cupy-cuda11x.

Restoration requires both cupy and cucim

Install cuCIM in the same Python environment used to run tiresias-deconvolve.

Observed image has no positive finite signal

The selected tile contains no usable positive signal after NaN/Inf cleanup. Check the input volume, channel selection, and background handling.

All chunks failed during PSF estimation

Reduce --chunk-xy, reduce --blind-z-slices, check GPU availability, and confirm that the input TIFF is a 3-D volume with non-zero signal.