Skip to content

biwt.types

The public API boundary — the types and constants that cross between a host application and BIWT.

For prose on how to use them well, see the API contract.

Shared data contracts between biwt.core, biwt.gui, and the host application.

Three types form the public API boundary:

DomainSpec  — spatial domain description passed IN to BIWT from the host.
BiwtInput   — the host's state, re-read at the start of every run.
BiwtResult  — everything BIWT returns to the host on completion.

How the widget itself is set up — the host's name, its starting template library, its name-matching rule — is passed to create_biwt_widget rather than living here. Those belong to the widget for its lifetime; re-asking for them per run would either overwrite what the user has done to them or be ignored.

Keeping these in one file makes the host ↔ package interface easy to audit.

BiwtInputSource module-attribute

BiwtInputSource = Union[BiwtInput, Callable[[], BiwtInput]]

What a host hands to :func:biwt.gui.create_biwt_widget: a :class:BiwtInput, or a zero-argument callable returning one.

Pass the callable when the host's domain or cell definitions can change after the widget is built — an embedded tab rather than a one-shot popup. BIWT calls it at the start of each run, so the host never has to find a lifecycle hook to push updates through, and never has to defend a getter against being read mid-edit.

HOST_SOURCE module-attribute

HOST_SOURCE = '<host>'

Reserved stand-in for a source path, meaning the host already defines this cell type.

It appears as the first element of a BiwtResult.cell_templates value when the user assigned a cell type one of the names the host passed in BiwtInput.host_cell_type_names rather than a template from a file. There is no template content in that case — the host holds the definition already — so the third element is the empty string.

Deliberately not a usable path: no filesystem accepts < or >, so a host that forgets to check it fails at once rather than reading a file that happens to exist. Compare against this constant, not against the literal::

from biwt.types import HOST_SOURCE

for cell_type, (path, name, content) in result.cell_templates.items():
    if path == HOST_SOURCE:
        reuse_existing_definition(cell_type, name)   # `name` is your own
    else:
        build_definition_from(cell_type, content)

What "reuse" means is the host's call. BIWT only reports the match.

DomainSpec dataclass

DomainSpec(
    xmin: float,
    xmax: float,
    ymin: float,
    ymax: float,
    zmin: float = -10.0,
    zmax: float = 10.0,
    source: str = DomainSource.HOST,
    units: str = "micron",
)

Spatial domain dimensions.

The units field records the coordinate system (default "micron", which is what PhysiCell uses). When BIWT is embedded in another host the units may differ. BIWT labels the domain editor with it and stamps it onto BiwtResult.domain_used; it neither converts nor compares units.

The source field records where these bounds came from, so the host can tell whether its own domain survived — see :class:DomainSource.

is_2d property

is_2d: bool

True when the z extent is ≤ one default PhysiCell voxel (20 µm).

default classmethod

default() -> 'DomainSpec'

±500 µm × ±10 µm — a last-resort box for when the host names none.

Source code in src/biwt/types.py
90
91
92
93
94
@classmethod
def default(cls) -> "DomainSpec":
    """±500 µm × ±10 µm — a last-resort box for when the host names none."""
    return cls(xmin=-500.0, xmax=500.0, ymin=-500.0, ymax=500.0,
               zmin=-10.0, zmax=10.0, source=DomainSource.DEFAULT)

DomainSource

Where a :class:DomainSpec's bounds came from.

Three answers matter to a host — its own domain, the data's, or the user's — so those are the three values. DEFAULT is the fourth because "nobody supplied one" is not the same as any of them: BIWT invents a box so the walkthrough can proceed, and it also marks a data domain as not real, which is how the positions step knows there is no mismatch worth asking about.

BiwtInput dataclass

BiwtInput(
    preferred_domain: DomainSpec = (
        lambda: DomainSpec.default()
    )(),
    host_cell_type_names: list = list(),
)

The host's state, as of the run about to start.

Two questions, both about what your application currently holds: which cell types it defines, and what domain it wants. Everything else BIWT needs from a host — its name, its template library, how it decides two names mean the same cell type — is set up once on the widget, not re-asked per run.

A long-lived host builds the widget once but the user may not run the walkthrough until much later, so BIWT resolves its input at the start of every run — the moment the user imports a file — and holds a :meth:snapshot of it until that run completes. A host whose values can change in the meantime passes a callable rather than an instance; see :data:BiwtInputSource.

Both fields are read afresh at each run, which is what keeps that contract simple: nothing here can be set and then quietly ignored.

Parameters:

Name Type Description Default
preferred_domain DomainSpec

Domain spec from the host's current configuration. BIWT will use this unless it discovers richer spatial metadata in the imported data.

(lambda: default())()
host_cell_type_names list

Cell type names currently defined in the host (e.g. a cell-definitions tab). BIWT does not require them, and never constrains the user to them. Used twice: as rename suggestions, and as candidates at the cell-templates step — assigning one means "the host already has this cell type", which comes back marked with :data:HOST_SOURCE rather than a file path. They match on equal footing with templates from files.

list()

snapshot

snapshot() -> 'BiwtInput'

A copy BIWT can hold for a whole run without it moving underneath.

replace re-runs __post_init__, which copies the name list; the domain is copied here because it is a mutable dataclass of its own. A host that edits its own objects would otherwise rewrite BiwtResult.domain_used after the cells were placed against the old numbers.

Source code in src/biwt/types.py
188
189
190
191
192
193
194
195
196
def snapshot(self) -> "BiwtInput":
    """A copy BIWT can hold for a whole run without it moving underneath.

    ``replace`` re-runs ``__post_init__``, which copies the name list; the domain
    is copied here because it is a mutable dataclass of its own.  A host that
    edits its own objects would otherwise rewrite ``BiwtResult.domain_used``
    after the cells were placed against the old numbers.
    """
    return replace(self, preferred_domain=replace(self.preferred_domain))

BiwtResult dataclass

BiwtResult(
    coordinates: DataFrame,
    cell_type_map: dict,
    domain_used: DomainSpec,
    cell_templates: dict = dict(),
)

Everything BIWT returns to the host on workflow completion.

Parameters:

Name Type Description Default
coordinates DataFrame

DataFrame with columns ["x", "y", "z", "type"]. One row per placed cell.

required
cell_type_map dict

Maps each original data label to the final name used in coordinates. Values are None for deleted types.

required
domain_used DomainSpec

The DomainSpec that was actually applied when placing cells. domain_used.source tells the host whether this differs from what it passed in.

required
cell_templates dict

Maps a final cell-type name to the cell template chosen for it: (path, name, content) — the absolute path of the .toml file it came from, its key in that file, and that key's value verbatim, surrounding whitespace included.

content is opaque. BIWT reads it as text and never parses, validates or wraps it, so a PhysiCell host receives exactly the <phenotype> block its own template file holds and owns every decision about assembling a config from it.

Cell types the user left unassigned are absent, so {} is the normal result when the step was skipped: check membership per type rather than assuming full coverage. Two types may carry the same name from different files — path and name together identify a template, name alone does not.

dict()
Notes

Reserved for future expansion — these are not currently attributes and hosts must not code against them: substrate_data (DataFrame), gene_expression (DataFrame), spatial_metadata (dict).

to_csv

to_csv(path: str) -> None

Write coordinates to path as a four-column x,y,z,type CSV.

A convenience for the host, which owns the output location: the path is not recorded on the result. BIWT does not choose where anything goes.

Source code in src/biwt/types.py
285
286
287
288
289
290
291
def to_csv(self, path: str) -> None:
    """Write ``coordinates`` to *path* as a four-column ``x,y,z,type`` CSV.

    A convenience for the host, which owns the output location: the path is
    not recorded on the result.  BIWT does not choose where anything goes.
    """
    self.coordinates[["x", "y", "z", "type"]].to_csv(path, index=False)