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,YoungDupreonly): the mean, per junction line, of the seeds' line-averaged contact angles and line weights, then oneYoungDupresolve. Withangle_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 |
seconds |
float
|
wall time of the reconstruction and inference of this seed. |
warnings |
tuple[str, ...]
|
the warnings this seed raised, as |
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 |
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 |
tension_seed_sd |
dict[Interface, float]
|
standard deviation ( |
relative_tension_seed_sd |
dict[Interface, float]
|
|
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]
|
|
per_seed_tensions |
dict[int, dict[Interface, float]]
|
each seed's own tension map ( |
reconstructions |
tuple[SeedReconstruction, ...]
|
one :class: |
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 |
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 |
3
|
mode
|
TensionComputationMethod
|
the tension method, |
YoungDupre
|
angle_reading
|
str
|
the angle reading of the angle-reading methods, |
CHORD_READING
|
combine
|
str
|
|
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
|
None
|
progress
|
Callable[[int, int, int], None] | None
|
called once per seed, as its tensions come
back and in seed order, with |
None
|
**reconstruction_options
|
object
|
|
{}
|
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 |
Raises:
| Type | Description |
|---|---|
ValueError
|
on an empty or repeated seed list, an unknown |
TypeError
|
on an unknown reconstruction option. |
Warns:
| Type | Description |
|---|---|
SeedEnsembleExperimentalWarning
|
with |
UserWarning
|
every warning a seed's reconstruction or inference emits (for example
|