Skip to main content

Bayesian MEF

PyPI Python 3.11+ DOI License

Bayesian multi-exposure image fusion (MEF) is a general-purpose algorithm for robust high dynamic range (HDR) imaging under low SNR or varying illumination — in particular for phase retrieval in coherent diffractive imaging. The paper, "Bayesian multi-exposure image fusion for robust high dynamic range ptychography", details the method and its benefits for ptychography (reproducing the results).

demo_mef

This small library is implemented in JAX, so the same code runs on CPU/GPU/TPU. Inputs may be NumPy arrays, Python lists or JAX arrays; results come back as NumPy arrays.

pip install bayes_mef

Optionally for GPU (CUDA 12 or 13)

pip install "bayes_mef[cuda13]"  # GPU with CUDA 13; `cuda12` flag for older GPUs

After a GPU install, check the GPU is picked up with bayes_mef check gpu.

Usage

A minimal example, simulating some data (Colab):

from bayes_mef import BayesianMEF, ConventionalMEF
from skimage.data import camera
import numpy as np

truth = camera()
background = 60
times = np.array([0.1, 1, 10])  # exposure times / flux factors
threshold = 1500                # detector limit

# overexposed Poisson data from the image-formation model
data = [np.random.poisson(t * truth + background) for t in times]
data_saturated = np.clip(data, None, threshold, dtype="float")

mef_em = BayesianMEF(data_saturated, threshold, times, background)
mef_em.run(n_iter=100)
fused_em = mef_em.fused_image.copy()

# ConventionalMEF is the paper's MLE baseline, with the same interface
mef_mle = ConventionalMEF(data_saturated, threshold, times, background)
mef_mle.mle()
fused_mle = mef_mle.fused_image.copy()

Precision

Single precision by default (accurate for censoring thresholds up to ~4096). For 16-bit detectors, switch to double precision before creating any array; a warning flags the risk otherwise:

import bayes_mef
bayes_mef.enable_x64()  # or set JAX_ENABLE_X64=1

When the exposures are not known

Omit times (and threshold) and they are estimated from the data, initialised from the non-saturated pixels (flux_init="matched"). The estimate is only a starting point, so pass update_fluxes=True with it and let EM refine it:

mef_em = BayesianMEF(data_saturated, background=background, update_fluxes=True)
mef_em.run(n_iter=200)

update_fluxes defaults to False — times are used exactly as given, estimated or not — and estimating them without it warns. On the simulation above, the unrefined estimate correlates 0.16 with the truth against 0.989 once refined. flux_init="uncensored" selects the v0.1.9 initialiser, whose ratios compress under heavy censoring.

Once the background dominates the signal, i.e., the weak, low-SNR regime the paper targets: summed counts carry almost no information about the exposures, and it is EM's iterative background handling that recovers the range; a warning is raised when the estimate comes out nearly flat. Estimated fluxes are relative, so the fused image is on a relative scale. Supply the real exposure times whenever you know them.

Fusing a 4D ptychogram dataset

For ptychography, we record multiple exposures per scan position. LaunchMEF fuses every scan position with a single vectorised program (CPU or GPU):

from bayes_mef import LaunchMEF

launch_mef = LaunchMEF(
    ptychogram_stack,    # (n_exposures, n_scans, dp_x, dp_y)
    background,          # a number, one dark frame, or one per exposure
    times=None,          # None -> estimated from the data
    threshold=None,      # None -> estimated automatically
    update_fluxes=False, # True -> EM refines the fluxes; pair with estimated times
    flux_init="matched",
)

# returns fused patterns (n_scans, dp_x, dp_y) and the flux factors
fused_ptyem_stack, em_flux_factors = launch_mef.run_em(n_iter=150)

# or the conventional MLE baseline over the whole stack (just the fused patterns)
fused_ptymle_stack = launch_mef.run_mle()

Backgrounds are taken however they were recorded, on LaunchMEF and on the single stack classes alike: a scalar, one offset per exposure (n_exposures,), a single dark frame (dp_x, dp_y), one frame per exposure (n_exposures, dp_x, dp_y), or one per image (the full stack shape). Each is given its exposure axis explicitly, so a per-exposure vector is never spread along the image columns, and a mismatched shape raises instead of broadcasting into something that quietly means the wrong thing.

The old n_cpus argument is accepted but ignored (it warns), since there are no worker processes to size any more.

Scans are fused in chunks sized to the device's free memory, so a stack larger than device memory still works. Set batch_size yourself if you hit a MemoryError or fuse alongside other work (results do not depend on it):

fused, fluxes = launch_mef.run_em(n_iter, batch_size=8)

See synthetic_mef.py for detailed usage on synthetic ptychography data, and benchmarks/FINDINGS.md for a study of when each method helps and for performance benchmarks.

Reproducing results

To reproduce the ptychographic reconstructions from the paper:

  1. Clone the repo:
    git clone https://github.com/microscopic-image-analysis/bayes-mef.git
    cd bayes-mef
    
  2. Install the pinned dependencies with uv, then prefix commands with uv run (e.g. uv run python scripts/synthetic_mef.py):
    uv sync --locked --group scripts
    
  3. Download the data from Zenodo:
    ./download_data.sh
    
  4. Optional: install cupy for faster GPU reconstructions.
  5. Run files from scripts/ to plot the results.

Citation

If this algorithm or the publication was useful, please cite:

@article{Kodgirwar:24,
author = {Shantanu Kodgirwar and Lars Loetgering and Chang Liu and Aleena Joseph and Leona Licht and Daniel S. Penagos Molina and Wilhelm Eschen and Jan Rothhardt and Michael Habeck},
journal = {Opt. Express},
number = {16},
pages = {28090--28099},
publisher = {Optica Publishing Group},
title = {Bayesian multi-exposure image fusion for robust high dynamic range ptychography},
volume = {32},
month = {Jul},
year = {2024},
url = {https://opg.optica.org/oe/abstract.cfm?URI=oe-32-16-28090},
doi = {10.1364/OE.524284},
}

Metadata

Release files for bayes-mef 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bayes-mef 0.2.0
File Size Uploaded Provenance Actions
bayes_mef-0.2.0.tar.gz 32.6 kB With provenance Details for bayes_mef-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for bayes-mef 0.2.0
File Interpreter ABI Platform Actions
bayes_mef-0.2.0-py3-none-any.whl Python 3 none any Details for bayes_mef-0.2.0-py3-none-any.whl

Total release size: 58.6 kB

Release files / bayes_mef-0.2.0.tar.gz

Download URL bayes_mef-0.2.0.tar.gz
Size 32.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cfb4be97dfb362e80a94d8515ba05f632a0e01e7de89f982d36b5239ded2320e
BLAKE2b-256 checksum
How to use checksums
070d3b8684cee6fdb47777ea204fcdf47662618cbb53cf6e068381d3b135db0d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release files / bayes_mef-0.2.0-py3-none-any.whl

Download URL bayes_mef-0.2.0-py3-none-any.whl
Size 25.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
73254a9162a91d9f19a6d1c9973b473fd7dcf7ff0aa9692e6b614f8cdb9314b6
BLAKE2b-256 checksum
How to use checksums
cf6405ac153b89c7736e2991b2437072e29a6e42d54a66506b10d5f557f88561
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Security for bayes-mef 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for bayes_mef-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page