Type System

BioImageFlow uses Python’s Annotated types to describe image data flowing through the pipeline. The type system catches incompatibilities at graph-construction time, before any computation runs.

ImageSpec

ImageSpec is a frozen dataclass with four optional constraint sets:

@dataclass(frozen=True)
class ImageSpec:
    semantics: frozenset[str] = frozenset()  # what pixels represent
    layouts: frozenset[str] = frozenset()     # axis ordering
    dtypes: frozenset[str] = frozenset()      # numpy dtypes
    formats: frozenset[str] = frozenset()     # "memory" or file formats

An empty set means wildcard — any value is accepted.

Semantic

Semantic describes what the pixel values represent:

Value

Meaning

BINARY

Binary mask (0/1)

LABEL

Instance or semantic label map (integers)

INTENSITY

Raw intensity image

PROBABILITY

Probability map (0.0–1.0)

DISPLACEMENT

Vector field / displacement map

FEATURE

Feature map (e.g., embeddings)

Semantic Groups

SCALAR_IMAGE_SEMANTICS is a convenience set for consumers that accept displayable scalar raster images without requiring one specific pixel meaning:

from pathlib import Path
from typing import Annotated

from bioimageflow_core import ImageSpec, SCALAR_IMAGE_SEMANTICS

image: Annotated[Path, ImageSpec(semantics=SCALAR_IMAGE_SEMANTICS)]

It contains INTENSITY, BINARY, LABEL, and PROBABILITY. It intentionally excludes DISPLACEMENT and FEATURE. This is useful for visualization and montage tools; algorithms that require raw physical values should still declare Semantic.INTENSITY specifically.

Layout

Layout describes the axis ordering:

Value

Axes

ndim

PLANAR

YX

2

PLANAR_CHANNEL

CYX

3

PLANAR_TIME

TYX

3

PLANAR_TIME_CHANNEL

TCYX

4

VOLUMETRIC

ZYX

3

VOLUMETRIC_CHANNEL

CZYX

4

VOLUMETRIC_TIME

TZYX

4

VOLUMETRIC_TIME_CHANNEL

TCZYX

5

Image Fields and ImageShared

File-based image fields are declared as Annotated[Path, ImageSpec(...)]. ImageShared is a factory function for shared-memory image fields:

from pathlib import Path
from typing import Annotated

from bioimageflow_core import Connectable, GUIMeta, ImageShared, ImageSpec

image: Annotated[
    Path,
    ImageSpec(semantics={"intensity"}, layouts={"YX", "CYX"}),
]

visible_image: Annotated[
    Path,
    ImageSpec(semantics={"intensity"}),
    GUIMeta(
        display_name="Input image",
        connectable=Connectable.BY_DEFAULT,
    ),
]

# Shared-memory image:
# Annotated[SharedArray, ImageSpec(..., formats={"memory"})]
image: ImageShared(semantics={"intensity"})

Use Annotated[Path, ImageSpec(...)] for file-based I/O and ImageShared() for zero-copy shared memory. Add GUIMeta as another Annotated metadata entry when the field needs GUI hints.

Compatibility checking

check_compatibility() validates that a producer’s output type is compatible with a consumer’s input type. The rules use asymmetric wildcard semantics:

  1. Empty consumer set = accept anything (wildcard)

  2. Empty producer set + non-empty consumer set = warn but accept

  3. Both non-empty = sets must intersect

from bioimageflow_core import ImageSpec, check_compatibility

producer = ImageSpec(semantics=frozenset({"label"}))
consumer = ImageSpec(semantics=frozenset({"label", "binary"}))

check_compatibility(producer, consumer)  # True: {"label"} & {"label", "binary"} = {"label"}

consumer_strict = ImageSpec(semantics=frozenset({"binary"}))
check_compatibility(producer, consumer_strict)  # False: {"label"} & {"binary"} = {}

Semantic groups do not add subtype rules. For example, a binary producer is still incompatible with a strict intensity consumer; the consumer must declare SCALAR_IMAGE_SEMANTICS or an explicit set containing BINARY.

This checking happens automatically when you bind a column reference to an input — you don’t need to call it manually.

GUIMeta

GUIMeta attaches GUI hints to Inputs and Outputs fields using the same Annotated mechanism as ImageSpec. A separate GUI can introspect these hints to render appropriate widgets, labels, and tooltips.

from pathlib import Path
from typing import Annotated

from bioimageflow_core import Connectable, GUIMeta, ImageSpec, Semantic, Template

class Inputs(IOModel):
    image: Annotated[
        Path,
        ImageSpec(semantics={Semantic.INTENSITY}),
        GUIMeta(
            display_name="Input image",
            description="Fluorescence image to segment.",
            connectable=Connectable.BY_DEFAULT,
        ),
    ]
    diameter: Annotated[float, GUIMeta(
        display_name="Cell diameter",
        description="Approximate cell diameter, in pixels.",
        min=1.0, max=500.0, step=0.5,
    )] = 30.0

Parameters:

  • display_name (str | None): human-readable label shown in the GUI. When None, frontends fall back to the field name.

  • description (str | None): longer help text for tooltips / inline help, describing what the field means and when to change it.

  • connectable (Connectable, default Connectable.NOT_BY_DEFAULT): controls pin visibility for Inputs fields. NEVER hides the pin entirely, NOT_BY_DEFAULT shows it only when toggled via checkbox, BY_DEFAULT shows it out of the box. Ignored for Outputs fields, which always expose a pin.

  • min / max (float | None): numeric bounds for the widget.

  • step (float | None): step increment for spinbox or slider widgets.

  • group (str | None): logical group name for tabs or sections (e.g. "general", "advanced", "gpu").

Fields without GUIMeta default to Connectable.NOT_BY_DEFAULT with no label, description, numeric bounds, or group. Data input fields (image paths) should use explicit GUIMeta(connectable=Connectable.BY_DEFAULT) to show their pins.

Output fields can also carry GUIMeta so the GUI can label output pins and show tooltips:

class Outputs(IOModel):
    mask: Annotated[
        Path,
        ImageSpec(semantics={Semantic.LABEL}),
        GUIMeta(
            display_name="Segmentation mask",
            description="Label image; each cell gets a unique ID.",
        ),
    ] = Template("{input_image.stem}_mask{ext}")
    cell_count: Annotated[int, GUIMeta(
        display_name="Cell count",
        description="Number of cells detected.",
    )]

GUIMeta and ImageSpec coexist as separate Annotated metadata entries:

image: Annotated[
    Path,
    ImageSpec(semantics={Semantic.INTENSITY}),
    GUIMeta(connectable=Connectable.BY_DEFAULT),
]

Introspection

Programmatic schema introspection (get_inputs_schema, serialize_input_schema, serialize_output_schema, the Connectable serialization, and the wire-format helpers) lives in the GUI tree — see Tool Schemas.