Skip to content

Seed ensembles

Tensions from several reconstructions of one mask: seed ensembles.

delaunay-watershed-3d places its tessellation points with a small random dither, fixed by a seed (42 by default). Two seeds give two slightly different meshes of the same mask, and the tensions inferred from them differ. How much depends on cell size: measured with the default reconstruction (min_distance 3, junction relocation on) and YoungDupre with the chord reading, a tension varies from seed to seed by

  • about 5 % (median coefficient of variation) on the 42 equilibrium cases of the benchmarking dataset, whose cells are about ten times larger in volume than real ones;
  • about 13-14 % on synthetic tissues voxelised at a real cell size (median cell 69 370 voxels, radius ~25) and on embryo frames;
  • about 21 % on a 110-cell and on a 1 866-cell tissue meshed on their native anisotropic grid.

Averaging the tensions of several seeds removes part of that variation. Against ground truth, an ensemble of 8 seeds lowers the median relative tension error (gauge-matched, median over interfaces, then over tissues) from 0.152 to 0.123 on 12 synthetic tissues at real cell size (-23 % per tissue, lower on all 12), and from 0.077 to 0.064 on the benchmark (-9 %, lower on 35 of 42 cases), where most of the error is bias rather than seed noise. The contact-angle error falls by about 2 degrees at real cell size.

What this module offers. :func:infer_tensions_over_seeds reconstructs a mask at a fixed list of seeds (0 to 7 by default), infers the tensions of each mesh, and combines them:

  • combine="tensions" (the default): the mean, per interface, of the seeds' tensions. Each seed's solve meets the normalisation (a mean tension of 1) exactly, so the means share one scale.
  • combine="angles" (experimental, YoungDupre only): the mean, per junction line, of the seeds' line-averaged contact angles and line weights, then one YoungDupre solve. With angle_reading="quadric" it was the most accurate ensemble measured on synthetic tissues with known tensions, voxelised at the cell size of real embryos (median tension error 45 % below the default ensemble's on 46 tissues), but its margin over averaging quadric-read tensions is small and inconsistent, and with the chord reading it is inconsistent on the larger tissues. On synthetic masks degraded by a smooth random warp of about one voxel, combine="angles" with the quadric reading lost its advantage over the default on larger tissues of 90 and 165 cells while keeping it on small aggregates. It is not the default; it warns with :class:SeedEnsembleExperimentalWarning. Use it as a comparison, not as a replacement.

The seed list is fixed by the caller and never chosen on a result. Choosing the seed that gives the "best-looking" tensions would tune on the quantity being measured. The default list is 0 to 7. Seed 42, the default of every single-seed function, is not in it, so an ensemble never silently includes the default mesh. seeds=[42] reproduces :func:foambryo.infer_tensions on :func:foambryo.dcel_mesh_from_segmentation_mask's mesh, bit for bit.

The seed spread is a lower bound on the error, not an error bar. tension_seed_sd is the standard deviation over the seeds. It sees the part of the error that changes with the seed, never the bias every seed shares. Against ground truth at real cell size, a single seed's ±1 SD interval contained the true tension for about half of the interfaces (nominal 68 %), and on the benchmark for about a third.

Interfaces found by only some seeds. A seed can create an interface that another seed does not (two cells that barely touch, or do not touch at all). At real cell size about a fifth of the interfaces are found by only some of 16 seeds. On synthetic tissues with known geometry, most of those found by fewer than half of the seeds do not exist in the true tissue. They are kept in the result with the flag found_by_fewer_than_half_the_seeds; read their tensions with that in mind.

Cost. One reconstruction per seed, so N times the single-seed cost. Measured per seed:

tissue time peak memory
benchmark case ~3 s ~1.5 GiB
165-cell tissue at real cell size ~40-65 s ~8 GiB (up to 11)
1 866-cell tissue ~11 min ~30-35 GiB

max_workers runs seeds in parallel processes. Each holds one reconstruction in memory, so choose it from the memory, not only from the number of cores.

SeedEnsembleExperimentalWarning

Bases: UserWarning

Emitted by infer_tensions_over_seeds(..., combine="angles"), which is experimental.

Its advantage over the default combine="tensions" was small and inconsistent on synthetic tissues with known tensions, and was lost on larger tissues degraded by a smooth random warp; see foambryo.seed_ensembles.

SeedReconstruction dataclass

SeedReconstruction(seed: int, n_points: int, n_triangles: int, n_crop_faces: int, seconds: float, warnings: tuple[str, ...])

What one seed's reconstruction produced.

Attributes:

Name Type Description
seed int

the dither seed.

n_points int

mesh vertices.

n_triangles int

mesh triangles.

n_crop_faces int

triangles on a face of the image box that the tissue reaches (see foambryo.crop_faces).

seconds float

wall time of the reconstruction and inference of this seed.

warnings tuple[str, ...]

the warnings this seed raised, as "Category: message", in order. All but deprecation notices are also re-emitted, prefixed with the seed, in seed order, whatever max_workers is.

SeedEnsembleResult dataclass

SeedEnsembleResult(seeds: tuple[int, ...], mode: TensionComputationMethod, angle_reading: str, combine: str, tensions: dict[Interface, float], tension_seed_sd: dict[Interface, float], relative_tension_seed_sd: dict[Interface, float], n_seeds_found: dict[Interface, int], found_by_fewer_than_half_the_seeds: dict[Interface, bool], per_seed_tensions: dict[int, dict[Interface, float]], reconstructions: tuple[SeedReconstruction, ...])

Tensions combined over several reconstructions of one mask.

Every map is keyed by interface (label 1, label 2), label 1 < label 2, over the union of the interfaces the seeds found, in sorted order.

Attributes:

Name Type Description
seeds tuple[int, ...]

the seeds, in the order given.

mode TensionComputationMethod

the tension method.

angle_reading str

the angle reading the angle-reading methods used.

combine str

"tensions" or "angles".

tensions dict[Interface, float]

the ensemble tension, relative: each seed's tensions are normalised to a mean of 1 (in each connected piece of its junction graph), and so is the single "angles" solve. With "tensions", the mean over the seeds that found the interface with a finite value; with "angles", the single solve on the averaged angles. nan when no seed determined it.

tension_seed_sd dict[Interface, float]

standard deviation (ddof=1) of the seeds' own tensions, over the seeds that found the interface; nan when fewer than two did. A lower bound on the error, not an error bar (module docstring).

relative_tension_seed_sd dict[Interface, float]

tension_seed_sd / |tensions|, the seed spread as a fraction of the ensemble tension (0.1 = 10 %); nan where either is nan or the ensemble tension is 0. A lower bound on the relative error, like tension_seed_sd.

n_seeds_found dict[Interface, int]

how many seeds found the interface with a finite tension.

found_by_fewer_than_half_the_seeds dict[Interface, bool]

n_seeds_found < len(seeds) / 2. Most such interfaces do not exist in the true tissue (module docstring); they are kept, flagged.

per_seed_tensions dict[int, dict[Interface, float]]

each seed's own tension map (foambryo.infer_tensions on that seed's mesh).

reconstructions tuple[SeedReconstruction, ...]

one :class:SeedReconstruction per seed, in seed order.

interfaces property

interfaces: tuple[Interface, ...]

The interfaces of the result, sorted.

infer_tensions_over_seeds

infer_tensions_over_seeds(mask: ndarray, seeds: Iterable[int] = DEFAULT_ENSEMBLE_SEEDS, *, min_distance: int = 3, mode: TensionComputationMethod = TensionComputationMethod.YoungDupre, angle_reading: str = CHORD_READING, combine: str = COMBINE_TENSIONS, max_workers: int = 1, spacing: tuple[float, float, float] | None = None, progress: Callable[[int, int, int], None] | None = None, **reconstruction_options: object) -> SeedEnsembleResult

Reconstruct a mask at several dither seeds and combine the tensions inferred from each mesh.

See foambryo.seed_ensembles for what the seeds change, the measured gain, and why the seed spread is a lower bound on the error rather than an error bar: tension_seed_sd sees only the part of the error that changes with the seed, never the bias every seed shares.

Each seed's mesh is reconstructed as dcel_mesh_from_segmentation_mask reconstructs it, at that dither seed (crop faces included), and its tensions are infer_tensions(mesh, 1, mode, angle_reading=angle_reading): normalised to a mean of 1, chord-read unless angle_reading says otherwise, with the default solver options.

Parameters:

Name Type Description Default
mask ndarray

instance segmentation image, as for foambryo.dcel_mesh_from_segmentation_mask: 3-D integer labels, axes (z, y, x), label 0 the medium.

required
seeds Iterable[int]

the dither seeds, distinct; 0 to 7 by default. Fix the list in advance: never choose seeds by their result.

DEFAULT_ENSEMBLE_SEEDS
min_distance int

minimum distance between tessellation points, in voxels. Defaults to 3, as dcel_mesh_from_segmentation_mask. Keyword-only.

3
mode TensionComputationMethod

the tension method, YoungDupre by default. Any method works with combine="tensions"; combine="angles" needs YoungDupre. Keyword-only.

YoungDupre
angle_reading str

the angle reading of the angle-reading methods, "chord" by default. Keyword-only.

CHORD_READING
combine str

"tensions" (the default): average the seeds' tensions. "angles" (experimental, YoungDupre only, emits SeedEnsembleExperimentalWarning): average the contact angles over the seeds, then infer once. With angle_reading="quadric" it was the most accurate option on synthetic tissues (median error -45 % against the default on 46 tissues), but its advantage over averaging quadric-read tensions is small and inconsistent, so it is not the default. On synthetic masks degraded by a smooth random warp of about one voxel, combine="angles" with the quadric reading lost its advantage over the default on larger tissues of 90 and 165 cells while keeping it on small aggregates. Keyword-only.

COMBINE_TENSIONS
max_workers int

seeds reconstructed at once, in separate processes; 1 (the default) runs them one after another in this process. The result does not depend on it. Each worker holds one reconstruction: about 8 GiB for a 165-cell tissue at real cell size, about 30-35 GiB for a 1 866-cell tissue. Keyword-only.

1
spacing tuple[float, float, float] | None

the voxel size along the mask's axes 0, 1, 2, as for dcel_mesh_from_segmentation_mask: given, every seed's reconstruction resamples a mask with one longer axis to cubic voxels of the shortest spacing and returns the mesh in the spacing's unit (min_distance then counts cubic voxels). Defaults to None, the voxel grid. Keyword-only.

None
progress Callable[[int, int, int], None] | None

called once per seed, as its tensions come back and in seed order, with (seeds done, seeds in all, that seed), for a front end's progress display; it changes no number. Defaults to None. Keyword-only.

None
**reconstruction_options object

print_info and physical_image_faces, passed to the reconstruction as dcel_mesh_from_segmentation_mask passes them.

{}

Returns:

Name Type Description
SeedEnsembleResult SeedEnsembleResult

the ensemble tensions, the seed spread (absolute and relative to the tension; a lower bound on the error, not an error bar), how many seeds found each interface, the flag for those found by fewer than half, and each seed's own tensions and reconstruction record. Every map is keyed by interface (label_1, label_2), label_1 < label_2, over the union of the seeds' interfaces.

Raises:

Type Description
ValueError

on an empty or repeated seed list, an unknown combine, combine="angles" with a method other than YoungDupre, an unknown angle_reading, or max_workers < 1; and, from a seed's reconstruction or inference, as dcel_mesh_from_segmentation_mask and infer_tensions (for example a non-chord angle_reading with a method that reads no angle).

TypeError

on an unknown reconstruction option.

Warns:

Type Description
SeedEnsembleExperimentalWarning

with combine="angles".

UserWarning

every warning a seed's reconstruction or inference emits (for example JunctionGraphGaugeWarning), re-emitted in seed order with its own category and the prefix "seed N: ", whatever max_workers is; deprecation notices stay in the seed's record only. With combine="angles", JunctionGraphGaugeWarning for the single solve too.