biwt.core¶
Pure-Python logic with no Qt dependency: loading files, inferring the domain, placing cells, and reconciling cell-type edits.
Internal
These are not part of the public API, and signatures may change between releases.
If you are embedding BIWT, work through biwt.types and
create_biwt_widget instead.
biwt.core.data_loader¶
Reads .h5ad, .rds/.rda/.rdata, and .csv into a single BiwtData shape, so nothing
downstream needs to know the source format.
Unified single-cell data loader.
Supported formats
.h5ad AnnData (requires biwt[anndata]) .rds Seurat / SingleCellExperiment / SpatialExperiment via rpy2 + anndata2ri (requires biwt[seurat]) .rda / .rdata R workspace files (same rpy2 requirement, loaded with base::load(); the object of a supported class is used, and an ambiguous workspace is refused rather than guessed at) .csv Flat tabular; spatial coordinates inferred from column names
All paths return a BiwtData object with a common interface so downstream
core logic never needs to know the source format.
BiwtData
dataclass
¶
BiwtData(
obs: DataFrame,
obsm: dict = dict(),
spatial_location: Optional[str] = None,
file_path: str = "",
probability_columns: list = list(),
host_units_per_data_unit: Optional[float] = None,
)
Unified in-memory representation of imported single-cell data.
Attributes:
| Name | Type | Description |
|---|---|---|
obs |
DataFrame
|
Per-cell metadata DataFrame (cluster labels, cell-type columns, etc.). Analogous to AnnData.obs. |
obsm |
dict
|
Named coordinate arrays (e.g. |
spatial_location |
Optional[str]
|
Human-readable description of where spatial coordinates were found,
e.g. |
file_path |
str
|
Path the data was loaded from. |
probability_columns |
list
|
Obs columns that look like per-cell-type deconvolution probabilities. |
host_units_per_data_unit |
Optional[float]
|
Conversion factor (host units per one raw data-coordinate unit) that the
file itself provided, or |
LoadError
¶
LoadError(message: str, *, docs_url: Optional[str] = None)
Bases: Exception
Raised when a file cannot be loaded.
docs_url is set when the fix is an installation or environment change
(a missing optional dependency, a broken R stack) and is None when the
failure is about the file itself (unsupported extension, malformed CSV).
So a host displaying this error should render the link only when
docs_url is not None, rather than always linking to the setup docs.
Source code in src/biwt/core/data_loader.py
112 113 114 | |
load
¶
load(file_path: str) -> BiwtData
Load single-cell data from file_path and return a BiwtData.
Dispatches on file extension:
.h5ad → AnnData
.rds → R object saved with saveRDS()
.rda / .rdata → R workspace saved with save()
.csv → flat CSV
Raises:
| Type | Description |
|---|---|
LoadError
|
On unsupported format or read failure, with an actionable message. |
Source code in src/biwt/core/data_loader.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | |
biwt.core.domain¶
Domain inference and the two-tier mismatch classification that decides whether the domain editor opens on its own.
Domain inference logic.
Priority order for resolving the final DomainSpec:
1. preferred — host-supplied DomainSpec (always wins if provided)
2. the data — min/max of the raw coordinate arrays (obsm or obs columns),
used exactly as found. The units are reported generically as
"data unit" — BIWT infers no unit name from the data (a
pixels→host-units scale factor is applied later, visibly, in
the domain editor). Singular, like every other unit name:
DomainSpec.units holds "micron", not "microns".
3. default — ±500 µm × ±10 µm fallback
Public entry point: infer_domain(preferred, obs, obsm)
infer_domain
¶
infer_domain(
preferred: Optional[DomainSpec] = None,
obs=None,
obsm: Optional[dict] = None,
spatial_key: Optional[str] = None,
) -> DomainSpec
Return the best available DomainSpec given what the data provides.
Coordinates are used exactly as found; the reported units are the generic
"data unit" (BIWT infers no unit name — imagerow/imagecol are still
recognized and y-flipped, but not labeled "pixel").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
preferred
|
Optional[DomainSpec]
|
DomainSpec from the host. Returned immediately if not |
None
|
obs
|
AnnData / DataFrame of per-cell metadata. Checked for x/y(/z) columns when obsm yields nothing useful. |
None
|
|
obsm
|
Optional[dict]
|
Dict of named coordinate arrays (e.g. |
None
|
spatial_key
|
Optional[str]
|
Explicit key to use in |
None
|
Source code in src/biwt/core/domain.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
classify_domain_mismatch
¶
classify_domain_mismatch(
data: DomainSpec, preferred: DomainSpec
) -> Optional[str]
Classify the relationship between data coordinates and the preferred domain.
Returns:
| Type | Description |
|---|---|
'outside'
|
One or more data boundaries exceed the preferred domain — those cells would be excluded from the simulation. |
'small'
|
Data fits inside the preferred domain but covers < 50% of at least one axis, or < 50% of the 2-D area — cells would be very sparse. |
None
|
No significant mismatch. |
Source code in src/biwt/core/domain.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | |
resolve_obs_coord_cols
¶
resolve_obs_coord_cols(
columns: list[str],
) -> tuple[
Optional[str], Optional[str], Optional[str], bool
]
Resolve which obs columns hold spatial coordinates.
Returns (x_col, y_col, z_col, is_image_coords). Standard x/y/z
columns take priority; only when they are absent do we fall back to
pixel-space imagecol/imagerow columns (is_image_coords=True, no z axis).
Returns (None, None, None, False) when nothing usable is found.
Source code in src/biwt/core/domain.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | |
build_obs_coords
¶
build_obs_coords(
obs,
x_col: str,
y_col: str,
z_col: Optional[str],
is_image_coords: bool,
) -> np.ndarray
Build an (N, 2) or (N, 3) float coordinate array from obs columns.
When is_image_coords is True the y column is an image row index (increasing downward), so it is reflected about its maximum to give a y-up coordinate (top of image → largest y). The reflection preserves the coordinate range.
Source code in src/biwt/core/domain.py
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | |
biwt.core.positioning¶
Coordinate scaling and assembly of the final cells DataFrame.
Spatial coordinate transformation and IC DataFrame construction.
compute_spatial_placement sizes and centers the data's extent inside the
host's domain; the positions window applies the result to the coordinates it
plots. build_ic_dataframe then collapses the per-cell-type coordinate
dicts into the flat BiwtResult.coordinates DataFrame the host receives.
compute_spatial_placement
¶
compute_spatial_placement(
data_extent, domain_center, scale, is_2d
)
Placement rectangle for the spatial plotter.
The data is scaled uniformly by scale (host-units per data unit) and
centered at domain_center — a pure scale + translate, so aspect ratio is
always preserved. scale of 1.0 places the data at its own extent,
centered (no conversion).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_extent
|
Sequence |
required | |
domain_center
|
Sequence |
required | |
scale
|
Uniform multiplier applied to every axis extent. |
required | |
is_2d
|
When True use only x/y. |
required |
Returns:
| Type | Description |
|---|---|
``[x0, y0, w, h]`` (2-D) or ``[x0, y0, z0, w, h, d]`` (3-D): the placement
|
|
rectangle's origin (min corner) followed by its widths.
|
|
Source code in src/biwt/core/positioning.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | |
build_ic_dataframe
¶
build_ic_dataframe(
coords_by_type: dict[str, ndarray],
) -> pd.DataFrame
Convert per-cell-type coordinate arrays into the standard IC DataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coords_by_type
|
dict[str, ndarray]
|
|
required |
Returns:
| Type | Description |
|---|---|
DataFrame with columns ``["x", "y", "z", "type"]``.
|
|
A cell type whose array is empty contributes no rows — that is how a
|
|
zero-count type reaches the output: defined, but placing nothing.
|
|
Empty DataFrame (same columns, same dtypes) if no rows result at all.
|
|
Source code in src/biwt/core/positioning.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
biwt.core.cell_types¶
Keep / merge / delete bookkeeping, and the name matching behind the rename suggestions and the
template pre-selection. A host owns the "same cell type?" decision via
create_biwt_widget(name_matches=…); default_name_matches is what BIWT falls
back to.
Deciding whether two strings name the same cell type — purely data, no Qt.
The decision is the host's: it can supply a predicate via
create_biwt_widget(name_matches=…). default_name_matches is the
fallback BIWT ships,
and best_match is the one selection routine used by both rename hints
(suggest_name_mappings) and the cell-templates step.
default_name_matches
¶
default_name_matches(
a: str,
b: str,
cutoff: float = DEFAULT_NAME_MATCH_CUTOFF,
) -> bool
Whether a and b plausibly name the same cell type.
Digit runs must be equal, then SequenceMatcher ratio on the casefolded
strings must reach cutoff. Digits distinguish types rather than spell them
(M1/M2 Macrophage scores 0.92), so they gate similarity instead of
feeding it. A non-numeric qualifier is not caught (PD-1hi …/PD-1lo
…); a host curating such names supplies its own predicate.
Hosts replace this wholesale — and the cutoff with it — via
create_biwt_widget(name_matches=…). Rationale and the rejection table:
docs/integration/templates-and-matching.md.
Source code in src/biwt/core/cell_types.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
best_match
¶
best_match(
name: str,
candidates,
matches: Optional[Callable[[str, str], bool]] = None,
sort_key=alpha_key,
) -> Optional[str]
Return the candidate that names the same cell type as name, or None.
A case-insensitive exact match always wins — every candidate is tried at that tier before any is accepted on similarity alone. Within a tier the first in sort_key order wins: a predicate offers no way to rank, and sorting keeps the result independent of how the candidates were collected.
Pass sort_key to express a preference among candidates, e.g. one source
before another. matches defaults to :func:default_name_matches.
Source code in src/biwt/core/cell_types.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
suggest_name_mappings
¶
suggest_name_mappings(
data_labels: list[str],
host_names: list[str],
matches: Optional[Callable[[str, str], bool]] = None,
) -> dict[str, Optional[str]]
Suggest a host cell-type name for each data label.
Delegates to :func:best_match, so the notion of "same cell type" is the
one the host chose — see create_biwt_widget(name_matches=…).
Returns a dict {data_label: host_name | None}.
None means no suggestion was found.
These are only hints for pre-populating the GUI; the user can overwrite any of them. A future version will query a cell-type ontology / registry.
Source code in src/biwt/core/cell_types.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
biwt.core.templates¶
Reading cell template files, and choosing a starting template per cell type. BIWT ships no templates of its own and never parses their content.
Cell template files.
A template file is a TOML mapping of template_name = "content". The content
is opaque to BIWT: it is read as text, never parsed, validated or wrapped,
and reaches the host verbatim in BiwtResult.cell_templates. For a PhysiCell
host the content happens to be an XML <phenotype> block, but nothing here
knows or cares about that.
BIWT ships no templates of its own. Files come from the landing screen's library — seeded by the host at widget construction, edited by the user — or from the user at the cell-templates step.
load_templates_from_file
¶
load_templates_from_file(path: str) -> dict[str, str]
Load a TOML template file and return {name: content}.
Raises ValueError if any value is not a string — a stray table or number
would otherwise travel all the way to the host and fail there, long after
the file that caused it is out of sight.
Source code in src/biwt/core/templates.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
matched_candidates
¶
matched_candidates(
cell_types: list[str],
template_names: list[str],
matches: Optional[Callable[[str, str], bool]] = None,
host_names: Optional[list[str]] = None,
) -> dict[str, Optional[str]]
Per cell type, the candidate that names it, or None.
host_names are the cell types the host already defines
(BiwtInput.host_cell_type_names). They rank ahead of the templates, so
the host wins where both name a type equally well — but only within a match
tier, so an exact template match still beats a merely similar host name.
None means nothing named this cell type. What to do then — fall back to a
default, leave the type unassigned — is the caller's: it depends on which
source supplies the baseline, and only the caller knows sources.
Source code in src/biwt/core/templates.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |