Advanced Usage
This page shows three advanced skills for users who need to go beyond pre-defined steps:
Write custom processing functions and wire them into pipelines using the base Step class
Enable multithreaded execution for large plates
Implement advanced functional patterns for complex workflows
Learning Path:
If you are new to EZStitcher, start with the Basic Usage guide (beginner level)
Next, learn about custom pipelines with steps in Intermediate Usage (intermediate level)
Now you’re ready for this advanced usage guide with the base Step class
For integration with other tools, see Integrating EZStitcher with Other Tools
Understanding Pre-defined Steps
Pre-defined steps are simply wrapped versions of the base Step class with pre-configured parameters.
For example, when you use NormStep(), you’re actually using this under the hood:
# NormStep is equivalent to:
Step(
func=(IP.stack_percentile_normalize, {
'low_percentile': 0.1,
'high_percentile': 99.9
}),
name="Percentile Normalization"
)
Similarly, ZFlatStep wraps IP.create_projection with variable_components=['z_index'],
and CompositeStep wraps IP.create_composite with variable_components=['channel'].
You can create your own custom steps by following the same pattern. For more details, see: - Step for step configuration - Function Handling for function patterns - Steps for API reference
1. Creating custom processing functions
Custom functions receive a list of NumPy arrays (images) and must return the same‑length list. For details on function patterns, see Function Handling.
import numpy as np
from skimage import filters
def custom_enhance(images, sigma=1.0, contrast=1.5):
"""Gaussian blur + contrast stretch."""
out = []
for im in images:
blurred = filters.gaussian(im, sigma=sigma)
mean = blurred.mean()
out.append(np.clip(mean + contrast * (blurred - mean), 0, 1))
return out
# Use in a Step with any of the function patterns:
step = Step(func=custom_enhance) # Basic usage
step = Step(func=(custom_enhance, {'sigma': 2.0, 'contrast': 1.8})) # With arguments
2. Building an advanced custom pipeline
Below we denoise, normalise, enhance and then stitch — all with two concise pipelines.
from pathlib import Path
from ezstitcher.core.pipeline_orchestrator import PipelineOrchestrator
from ezstitcher.core.pipeline import Pipeline
from ezstitcher.core.steps import Step, NormStep, PositionGenerationStep, ImageStitchingStep, ZFlatStep, CompositeStep
from ezstitcher.core.image_processor import ImageProcessor as IP
# ---------- orchestrator ----------------------------------------
plate_path = Path("~/data/PlateA").expanduser()
orchestrator = PipelineOrchestrator(plate_path)
# ---------- helper functions -----------------------------------
def denoise(images, strength=0.5):
from skimage.restoration import denoise_nl_means
return [denoise_nl_means(im, h=strength) for im in images]
# ---------- position pipeline ----------------------------------
pos_pipe = Pipeline(
input_dir=orchestrator.workspace_path,
steps=[
ZFlatStep(method="max"), # Z-stack flattening
Step(func=(denoise, {"strength": 0.4})), # Custom denoising
NormStep(), # Normalization (replaces Step(func=IP.stack_percentile_normalize))
CompositeStep(), # Channel compositing
PositionGenerationStep(), # Position generation
],
name="Position Generation",
)
positions_dir = pos_pipe.steps[-1].output_dir
# ---------- assembly pipeline ----------------------------------
asm_pipe = Pipeline(
input_dir=orchestrator.workspace_path,
output_dir=Path("out/stitched"),
steps=[
Step(func=(denoise, {"strength": 0.4})), # Custom denoising
NormStep(), # Normalization (replaces Step(func=IP.stack_percentile_normalize))
ImageStitchingStep(positions_dir=positions_dir), # Image stitching
],
name="Assembly",
)
orchestrator.run(pipelines=[pos_pipe, asm_pipe])
3. Channel‑aware processing with group_by='channel'
def process_dapi(images):
return IP.stack_percentile_normalize([IP.tophat(im, size=15) for im in images])
def process_gfp(images):
return IP.stack_percentile_normalize([IP.sharpen(im, sigma=1.0, amount=1.5) for im in images])
channel_step = Step(func={"1": process_dapi, "2": process_gfp}, group_by="channel")
Important
The interplay between group_by and variable_components controls how your function loops.
See Step and Function Handling for detailed explanations.
4. Conditional processing based on context
The context dict is passed to every Step when pass_context=True.
def conditional(images, context):
if context["well"] == "A01":
return process_control(images)
return process_treatment(images)
cond_step = Step(func=conditional, pass_context=True)
5. Multithreading for large plates
from ezstitcher.core.config import PipelineConfig
cfg = PipelineConfig(num_workers=4) # use 4 threads
orchestrator = PipelineOrchestrator(plate_path, config=cfg)
orchestrator.run(pipelines=[pos_pipe, asm_pipe])
Threads are allocated per well; inside a well, steps run sequentially. Adjust num_workers to avoid memory exhaustion.
6. Adding a new microscope handler
Implement BaseMicroscopeHandler and register it via register_handler.
See Extending EZStitcher for the full walkthrough.
EZ module → Quick wins with minimal code for standard plates
Custom pipelines → Full control for specialized workflows and research prototypes
For more information on the three-tier approach and when to use each approach, see the three-tier-approach section in the introduction.
Next steps
Read the Integrating EZStitcher with Other Tools guide for BaSiCPy and N2V2 (Careamics) integration examples
Review ../concepts/best_practices for pipeline organization and optimization tips
Explore Architecture Overview to understand core concepts in greater detail