Tools

BioImageFlow has two tool types, each suited to different kinds of operations.

Process boundaries

Choose the tool type by process boundary first:

  • Use ProcessingTool for row-wise or batched work that should run in an isolated worker environment.

  • Use DataFrameTool for main-process dataframe loading, filtering, merging, and aggregation.

Tool-specific dependencies for ProcessingTool classes must be imported inside process_row or process_batch. Module import must stay light so schemas can be inspected by the orchestrator, docs, tests, and GUIs without installing every worker dependency. Imports from the Python standard library and bioimageflow-core are safe at module level.

ProcessingTool

ProcessingTool is the workhorse of BioImageFlow. It runs in an isolated environment and processes data row-by-row or in batches.

When to use: image processing, segmentation, feature extraction, measurement — anything that operates on individual images or arrays.

from pathlib import Path
from typing import Annotated

from bioimageflow_core import GENERAL_ENV, ImageSpec, Template

class MyTool(ProcessingTool):
    display_name = "My Tool"
    environment = GENERAL_ENV  # or a custom EnvironmentSpec for specialized deps

    class Inputs:
        image: Annotated[Path, ImageSpec()]
        threshold: float = 0.5

    class Outputs:
        mask: Annotated[Path, ImageSpec(semantics={"binary"})] = Template(
            "{image.stem}_mask.tif"
        )

    def process_row(self, arguments: Arguments) -> "MyTool.Outputs":
        ...

Key properties:

  • Isolated execution: each tool declares an EnvironmentSpec. Use GENERAL_ENV for tools that only need standard scientific packages (numpy, scipy, scikit-image, imageio, tifffile, Pillow) or the Python standard library. Simple download, path, CSV/table, and file utility tools should use GENERAL_ENV instead of declaring one-off environments. Tools with specialized dependencies declare their own EnvironmentSpec.

  • Row-level parallelism: process_row is called once per row, enabling future parallel execution.

  • Batch mode: override process_batch for GPU-batched operations.

  • Explosion: return a list from process_row to produce multiple output rows (e.g., tiling).

DataFrameTool

DataFrameTool runs in the main process and transforms entire DataFrames. It has access to pandas and pydantic.

When to use: loading data, filtering rows, reshaping tables, combining results, computing aggregate statistics.

class MyTransform(DataFrameTool):
    display_name = "My Transform"

    class Inputs:
        min_area: float = 100.0

    def transform(self, df, arguments):
        return df[df["area"] >= arguments.min_area]

Key properties:

  • Main-process only: has access to the full pandas DataFrame.

  • Merge control: override merge_dataframes to customize how multiple upstream DataFrames are combined.

  • Passthrough: use Passthrough outputs to signal that input columns are preserved.

  • Source tools: set accepts_upstream = False on tools that produce a DataFrame from constants alone (e.g. Files, Generate). Constructing a source tool with positional upstream arguments raises SourceToolUpstreamError. See Graph Construction for the source-node patterns.

Dynamic output schemas

Most tools declare their output columns statically on the Outputs class. Two cases need a dynamic schema — output column names that depend on runtime values:

Inputs-driven schema — override resolve_outputs(cls, inputs) when the column names come from constant parameters. Generate is the canonical example, where column_name is a runtime parameter:

from bioimageflow_common_tools import Generate

gen = Generate()
sweep = gen(column_name="x", values=[0.1, 0.5, 1.0])

sweep["x"]            # OK — column_name resolved at construction time
sweep["unknown"]      # raises ColumnNotFoundError immediately

Upstream-driven schema — built-in merge tools (InnerJoin, CrossJoin, JoinOnColumn, Concat, Collect) override resolve_merge_schema(cls, upstream_schemas, inputs) instead, because their output columns depend on the upstream schemas, not on their own inputs.

A node’s effective schema is available via Node.get_output_schema(), and is also what node["col"] consults for construction-time validation.

IOModel

IOModel is the lightweight base class for Inputs and Outputs. It’s not pydantic — it’s a simple namespace with type annotations and validation.

class Inputs(IOModel):
    image: Annotated[Path, ImageSpec()]
    sigma: float = 1.0
  • Fields without defaults are required (must be bound to upstream columns or constants).

  • Fields with defaults are optional parameters.

  • Annotations are used for type checking and template resolution.

  • Attach GUIMeta to provide GUI hints (see Type System).

Arguments

Arguments is the namespace passed to process_row and process_batch. It contains resolved values for all input and output fields:

def process_row(self, arguments: Arguments):
    arguments.image    # Path to the input image
    arguments.sigma    # float, resolved from constant or column
    arguments.mask     # Path, resolved from output template

If you access a non-existent attribute, Arguments raises an AttributeError with close-match suggestions (via difflib).