Merge Strategies¶
When a tool needs data from multiple upstream nodes, the framework must combine
their DataFrames. bioimageflow-common-tools provides several merge
strategies as DataFrameTool subclasses.
InnerJoin (default)¶
InnerJoin joins DataFrames on their index. This is the
default behaviour when a ProcessingTool receives inputs from multiple upstream
nodes.
from bioimageflow import Workflow
from bioimageflow_common_tools import InnerJoin
join = InnerJoin()
with Workflow() as wf:
images = loader(folder="/data")
masks = segment(image=images["image"])
# Explicitly join two DataFrames
merged = join(images, masks)
result = wf.compute(merged)
CrossJoin¶
CrossJoin produces the Cartesian product of two
DataFrames. Useful for applying every parameter combination to every image:
from bioimageflow_common_tools import CrossJoin
cross = CrossJoin()
with Workflow() as wf:
images = loader(folder="/data") # 10 images
params = param_source() # 3 parameter sets
combos = cross(images, params) # 30 rows
results = process(image=combos["image"], sigma=combos["sigma"])
result = wf.compute(results)
Use suffixes to resolve column name conflicts:
cross = CrossJoin()
combos = cross(left_node, right_node, suffixes=("_img", "_param"))
JoinOnColumn¶
JoinOnColumn joins on a named column rather than the
index:
from bioimageflow_common_tools import JoinOnColumn
join = JoinOnColumn()
with Workflow() as wf:
images = loader(folder="/data")
metadata = csv_source(path="metadata.csv")
merged = join(images, metadata, join_column="sample_id", how="left")
result = wf.compute(merged)
Parameters:
join_column: the column to join on (required)how: join type —"inner","left","right","outer"(default:"inner")suffixes: tuple of suffixes for overlapping column names
Concat¶
Concat stacks DataFrames vertically. Use it to combine
results from parallel branches:
from bioimageflow_common_tools import Concat
concat = Concat()
with Workflow() as wf:
batch_a = loader(folder="/data/batch_a")
batch_b = loader(folder="/data/batch_b", name="loader_b")
all_images = concat(batch_a, batch_b)
result = wf.compute(all_images)
Collect¶
Collect gathers specific columns from multiple ancestor
nodes into a single DataFrame. Its output uses
Passthrough, preserving input columns:
from bioimageflow_common_tools import Collect
collect = Collect()
with Workflow() as wf:
raw = loader(folder="/data")
masks = segment(image=raw["image"])
stats = measure(mask=masks["mask"])
# Collect columns from different nodes
gathered = collect(raw, masks, stats)
result = wf.compute(gathered)
Choosing the right strategy¶
Strategy |
When to use |
|---|---|
InnerJoin |
Rows correspond 1:1 (same source, same indices) |
CrossJoin |
Every combination of rows (parameter sweeps) |
JoinOnColumn |
Match rows by a shared column value (e.g., sample ID) |
Concat |
Stack independent batches vertically |
Collect |
Gather columns from multiple points in the DAG |
Construction-time column validation¶
When both upstream nodes have a known schema, the merged schema is also known
at construction time, and node["col"] validates immediately. For example,
CrossJoin of two Generate
nodes resolves to the union of their declared column names:
from bioimageflow_common_tools import CrossJoin, Files, Generate
with Workflow():
files = Files()(path="./data")
sens = Generate()(column_name="sensitivity", values=[0.1, 0.2])
size = Generate()(column_name="size", values=[1, 2])
grid = CrossJoin()(files, sens, size)
# The merged schema {"path", "sensitivity", "size"} is
# resolved at construction time, so this raises immediately if you
# mistype the column name:
ref = grid["sensitivity"] # OK
# ref = grid["sensitvity"] # ColumnNotFoundError