Skip to main content

MINFLUX-like simulator

Project description

SimFlux — MINFLUX-like simulator (NPC and DNA-Origami)

Real MINFLUX - and single molecule localisation microscopy at large - datasets rarely have a perfect, known ground truth. This package generates synthetic SMLM datasets representative of NPC-like structures, or of DNA-Origami, with user-customised settings impacting replicating the effects of biolabelling, clutter, or the probability of oligomer chains. SimFlux aims to be explicit about what’s generated and how, so results are reproducible and assumptions are visible. The code focuses on two simple structures that cover a lot of benchmarking needs:

  • poly — regular polygons acting as monomers, assembled into linear oligomers with a spacing rule that enforces a single shared interface between neighbors.
  • grid — DNA‑origami–style point lattices (rows × cols) with a fixed node separation, randomly rotated and placed in a field of view.

Both modes share the same measurement model (label assignment, measurement noise, optional clutter) and the same I/O layout (in‑memory return or HDF5 on disk).


The Simulating Process

This section spells out the generation steps and how the parts fit together.

1) Centroids

A centroid anchors each object: either a monomer in a chain (poly) or a whole grid (grid). The number of groups to place is drawn from a Poisson with mean --centroid-mean, then each group attempts placement inside --xrange/--yrange with a hard‑core rule that prevents different groups from getting too close (distance threshold depends on the geometry: 2 × radius for poly, 2 × R for grid). Multiple attempts are made per group up to an internal budget; if no acceptable spot is found, the group is skipped.

2) Geometry per centroid

  • poly: for each chain, a polygon type is sampled from --mix, and an oligomer length (number of monomers) is sampled from --oligomers. Adjacent monomers are spaced so that one full edge is shared; optionally, the two shared vertices are snapped to coincide exactly (--enforce-shared-edge).
  • grid: a centered rows × cols lattice with node separation --sep is rotated by a random angle, then translated to the grid centroid. The effective grid radius R is computed from the farthest node offset.

3) Emitters

Every retained polygon vertex (poly) or lattice node (grid) becomes an emitter. Each emitter is independently tagged labelled with probability p (unlabelled otherwise). Two uncertainties can be introduced:

  • gt_uncertainty (ground‑truth jitter) perturbs the emitter anchors before any measurements are drawn.
  • ms_uncertainty (measurement noise) is used when drawing individual measurement locations.

4) Measurements

Each labelled emitter draws a Poisson number of measurement attempts (measured). Every attempt generates a noisy coordinate by adding Gaussian scatter with standard deviation ms_uncertainty to the emitter location. A Bernoulli filter with probability q keeps or discards each noisy point. Unlabelled emitters do not generate measurements.

5) Clutter (optional)

If clutter_fraction is set, a number of clutter clusters equal to floor(clutter_fraction × N_labelled) is created. Clutter cluster centers are sampled inside the bounding box of the emitters but are kept away from real centroids by an exclusion radius. Each clutter cluster then draws measurements using the same Poisson + Gaussian model (with the same measured and ms_uncertainty), but these points are tagged type="clutter" and have emitter_id = -1.

6) Membrane lifting (optional, on write)

If a callable membrane_function(x, y) is supplied and a filename is given, 2D coordinates are lifted to 3D by appending z = membrane_function(x, y) when writing the HDF5 file. In‑memory results remain 2D unless you explicitly apply the membrane first.

7) Audits & reproducibility

After placement, a quick hard‑core audit checks inter‑group centroid spacing and prints a short report if any pairs violate the rule. All top‑level routines accept a seed and set NumPy’s RNG accordingly inside the function for deterministic runs.


Features

  • Oligomer‑aware centroid placement with hard‑core spacing between different groups
  • Grid placement with automatic effective radius R
  • Shared‑edge enforcement for polygon chains
  • Configurable measurement model: p, q, measured, gt_uncertainty, ms_uncertainty
  • Optional clutter proportional to the number of labelled emitters
  • Optional membrane lifting to 3D on write
  • Compact, descriptive auto‑naming of output files
  • Plotly 2D quicklook
  • pytest test suite

Requirements

  • Python ≥ 3.9 (tested with 3.13)
  • NumPy ≥ 1.23
  • pandas ≥ 1.5
  • Plotly ≥ 5.0
  • h5py ≥ 3.7

Installation

Create and activate a fresh environment (example with venv):

python -m venv .venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate

Install in editable mode (recommended for development):

pip install -e .

Optionally, install dev tools:

pip install -e ".[dev]"

Quick verification:

python -c "import simflux, pkgutil; print(simflux.__file__)"

Project layout

simflux/
├─ simflux/               # Python package
│  ├─ __init__.py
│  └─ simflux.py          # simulator module (CLI & API)
├─ tests/                 # pytest tests
│  └─ tests.py
├─ pyproject.toml
└─ README.md

Run tests from the repository root so the package import resolves cleanly.


Command‑line usage

Global options (e.g. --plot) must appear before the subcommand (poly or grid) due to argparse rules.

python -m simflux.simflux \
  --xrange XMIN XMAX \
  --yrange YMIN YMAX \
  --centroid-mean MU \
  [--p P] [--q Q] [--measured LAMBDA] \
  [--gt-uncertainty SIG_GT] [--ms-uncertainty SIG_MS] \
  [--clutter-fraction FRACTION] \
  [--plot] [--seed SEED] \
  -o OUT_PREFIX \
  {poly|grid} ...

poly‑specific options

poly
  --radius R
  --mix "polygon:S@W[,polygon:S@W...]"
  [--oligomers "K:W[,K:W...]"]   # or short names: mono/di/tri/...
  [--enforce-shared-edge | --no-enforce-shared-edge]
  [--store-edges]

grid‑specific options

grid
  --grid ROWS COLS
  --sep SEPARATION

Examples

Octagons (poly mode)

Octagonal monomers, monomer‑only (no oligomers), modest noise, write HDF5 and show a 2D plot.

python -m simflux.simflux \
  --xrange 0 80 \
  --yrange 0 80 \
  --centroid-mean 6 \
  --p 0.8 \
  --q 0.9 \
  --measured 5 \
  --gt-uncertainty 0.05 \
  --ms-uncertainty 0.5 \
  --clutter-fraction 0.1 \
  --plot \
  --seed 42 \
  -o out/sim \
  poly \
  --radius 3.5 \
  --mix "polygon:8@1.0" \
  --oligomers "mono:1.0" \
  --enforce-shared-edge

This produces a file named like:

out/sim_poly-8_olig-mono1_p-0.8_q-0.9_cl-0.1_mu-6_R-3.5_gt-0.05_ms-0.5_SE_seed-42.h5

The 2D plot shows centroids, emitter anchors, observed points, and clutter (if any).


3×3 DNA‑origami grid (grid mode)

A 3×3 lattice with 10‑unit separation, moderate noise.

python -m simflux.simflux \
  --xrange 0 120 \
  --yrange 0 120 \
  --centroid-mean 4 \
  --p 0.75 \
  --q 0.8 \
  --measured 6 \
  --gt-uncertainty 0.05 \
  --ms-uncertainty 0.4 \
  --clutter-fraction 0.05 \
  --plot \
  --seed 99 \
  -o out/grid \
  grid \
  --grid 3 3 \
  --sep 10

The R term in the filename is the automatically computed effective radius of the grid.


Python API quickstart

from simflux.simflux import (
    simulate_poly, simulate_grid, plot2d,
    parse_mix, parse_oligomers
)

# Poly: octagons
geom_mix = parse_mix("polygon:8@1.0")
oligs    = parse_oligomers("mono:1.0")

poly_data = simulate_poly(
    filename=None,
    xrange=(0, 80), yrange=(0, 80),
    centroid_mean_groups=6,
    radius=3.5,
    geom_mix=geom_mix,
    oligomer_pmf=oligs,
    p=0.8, q=0.9, measured=5,
    gt_uncertainty=0.05, ms_uncertainty=0.5,
    clutter_fraction=0.1,
    membrane_function=None,
    enforce_shared_edge=True,
    store_edges=False,
    seed=42,
)
plot2d(data=poly_data)

# Grid: 3×3
grid_data = simulate_grid(
    filename=None,
    xrange=(0, 120), yrange=(0, 120),
    centroid_mean_groups=4,
    rows=3, cols=3, sep=10,
    p=0.75, q=0.8, measured=6,
    gt_uncertainty=0.05, ms_uncertainty=0.4,
    clutter_fraction=0.05,
    membrane_function=None,
    seed=99,
)
plot2d(data=grid_data)

Output data model (HDF5 schema)

When filename is provided, the following groups/datasets are written. In‑memory results mirror the same structure (positions are 2D unless a membrane is applied prior to write).

/centroid
  position    (C, 3) float   # x,y[,z]; z=0 if no membrane
  id          (C,)   int32
  group_id    (C,)   int32    # chain/grid id
  [poly only]
    group_index (C,) int32    # index within chain
    group_size  (C,) int32
    sides       (C,) int32    # polygon sides
    theta       (C,) float    # monomer orientation
  [grid only]
    theta      (C,) float     # grid rotation angle
    rows, cols (C,) int32
    sep, R     (C,) float

/ emitter
  position    (E, 3) float
  id          (E,)   int32
  centroid_id (E,)   int32
  type        (E,)   string   # "labelled" or "unlabelled"

/ observed
  position    (M, 3) float
  emitter_id  (M,)   int32
  centroid_id (M,)   int32

/ clutter
  position    (K, 3) float
  emitter_id  (K,)   int32    # always -1
  type        (K,)   string   # "clutter"

/ edges  [poly, optional]
  (P, 2) int32   # emitter id pairs per monomer if --store-edges

Reproducibility and seeding

  • simulate_poly, simulate_grid, and the centroid generators accept seed and reseed NumPy’s RNG inside the function. Two calls with the same arguments and the same seed produce identical results.
  • For tests that need deterministic outputs, pass a seed to the call. Global RNG state in the calling code remains predictable across separate runs.

Testing

Run everything:

pytest -q

Common issues:

  • String arrays (e.g., "labelled") should be compared with exact equality, not numeric closeness.
  • When running tests from an IDE, point the working directory to the repository root or ensure pip install -e . has been done in the active environment.

Troubleshooting

  • Global flags after subcommand: argparse won’t see them. Put flags like --plot before poly/grid.
  • Too few centroids placed: widen the field of view, reduce the hard‑core scale (smaller --radius for poly or --sep for grid), or reduce --centroid-mean.
  • Editable install: pip install -e .. Running pip install -e without a path is an error.

License

MIT License — include a plain‑text LICENCE file at the repository root. The pyproject.toml already declares the license.


A note on scope

This is a simulator for shape‑aware point patterns under a simple measurement model. It is not a physical fluorophore simulator, a photon budget model, or a microscope aberration engine. The goal is fast, controllable synthetic data for algorithm development and validation.

Future work shelved for future consideration includes time-series like spawned emitters to replicate STORM data, and a manual input scheme that accepts coordinates to build the structures. Presets for common structures appearing across SMLM are in the works, such as the 3-dimensional, double layer 8-fold symmetry of Nup96-SNAP.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simflux-0.1.1.tar.gz (30.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simflux-0.1.1-py3-none-any.whl (22.7 kB view details)

Uploaded Python 3

File details

Details for the file simflux-0.1.1.tar.gz.

File metadata

  • Download URL: simflux-0.1.1.tar.gz
  • Upload date:
  • Size: 30.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for simflux-0.1.1.tar.gz
Algorithm Hash digest
SHA256 bf8903b29a78becad72d6abbbc0b5c9d44b74670b6f5c6bf08e0bbdea9fda218
MD5 f755142035c22d7858280611329766f6
BLAKE2b-256 2d731204cde10b67c716e0ffebe07607c4e12e77d1600356ffde05933b45e4aa

See more details on using hashes here.

File details

Details for the file simflux-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: simflux-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 22.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for simflux-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4e799e510d3a73ac79d04f2e8f4a4dccee42c679e2a8e1810525743e514d8d41
MD5 b32db7743ef6bdd673491c2ec89b8559
BLAKE2b-256 99738dcd3a8ed18e084cd2a8f1ebac38f33d81687b4e5e88ec6a61c1f277ac14

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page