Pyclesperanto Simple Implementation
Module: openhcs.processing.backends.analysis.cell_counting_pyclesperanto_simple Status: STABLE
—
Overview
The simplified pyclesperanto cell counting implementation provides a direct, 334-line implementation of the Voronoi-Otsu labeling workflow with full OpenHCS materialization compatibility. This represents a 5x reduction in complexity compared to the original 1,578-line implementation.
Quick Reference
from openhcs.processing.backends.analysis.cell_counting_pyclesperanto_simple import (
count_cells_single_channel, count_cells_simple, DetectionMethod
)
# Full function (compatible with existing system)
output_stack, results, masks = count_cells_single_channel(
image_stack,
detection_method=DetectionMethod.VORONOI_OTSU,
gaussian_sigma=1.0,
min_cell_area=50,
max_cell_area=5000,
return_segmentation_mask=True
)
# Quick function (minimal parameters)
cell_count, positions = count_cells_simple(
image,
gaussian_sigma=1.0,
min_cell_area=50,
max_cell_area=5000
)
Voronoi-Otsu Workflow
The implementation follows the exact pyclesperanto reference workflow:
import pyclesperanto_prototype as cle
def voronoi_otsu_labeling(gpu_image):
"""Direct implementation of pyclesperanto reference workflow."""
# 1. Gaussian blur for noise reduction
blurred = cle.gaussian_blur(gpu_image, sigma_x=1.0, sigma_y=1.0)
# 2. Detect spots (cell centers)
spots = cle.detect_spots(blurred, radius_x=1, radius_y=1)
# 3. Otsu threshold for cell boundaries
binary = cle.threshold_otsu(blurred)
# 4. Masked Voronoi labeling (cell segmentation)
voronoi_labels = cle.masked_voronoi_labeling(spots, binary)
return voronoi_labels
Key insight: This workflow combines spot detection (cell centers) with Voronoi tessellation (cell boundaries) for robust cell segmentation.
Detection Methods
VORONOI_OTSU (Recommended)
output_stack, results, masks = count_cells_single_channel(
image_stack,
detection_method=DetectionMethod.VORONOI_OTSU,
gaussian_sigma=1.0,
min_cell_area=50,
max_cell_area=5000
)
Best for:
Round cells with clear boundaries
Fluorescence microscopy images
Cells with varying intensities
How it works:
Gaussian blur reduces noise
Spot detection finds cell centers
Otsu threshold identifies cell regions
Voronoi tessellation separates touching cells
THRESHOLD (Fallback)
output_stack, results, masks = count_cells_single_channel(
image_stack,
detection_method=DetectionMethod.THRESHOLD,
gaussian_sigma=1.0,
min_cell_area=50,
max_cell_area=5000
)
Best for:
High-contrast images
Well-separated cells
Quick testing and debugging
How it works:
Gaussian blur reduces noise
Otsu threshold creates binary mask
Connected components labels cells
Area filtering removes noise
Materialization Compatibility
The simplified implementation maintains full compatibility with OpenHCS materialization:
# Results are CellCountResult objects
result = results[0]
# Access cell count
print(f"Cell count: {result.cell_count}")
# Access cell positions (N x 2 array)
print(f"Positions: {result.cell_positions}")
# Access cell areas (N-element array)
print(f"Areas: {result.cell_areas}")
# Segmentation masks (labeled image)
mask = masks[0] # Each cell has unique label
Materialization outputs:
JSON summary: Cell counts per image
CSV details: Cell positions and areas
ROI extraction: Segmentation masks for all backends
Backend support: Disk, OMERO, Napari, Fiji streaming
Pipeline Integration
The simplified implementation integrates seamlessly with OpenHCS pipelines:
from openhcs.core.pipeline import Pipeline
from openhcs.core.steps import FunctionStep
# Create pipeline with cell counting step
pipeline = Pipeline(
steps=[
FunctionStep(
function='count_cells_single_channel',
parameters={
'detection_method': DetectionMethod.VORONOI_OTSU,
'gaussian_sigma': 1.0,
'min_cell_area': 50,
'max_cell_area': 5000,
'return_segmentation_mask': True
}
)
]
)
# Execute pipeline
pipeline.execute(plate_path)
Key insight: The simplified implementation uses the same materialization system as the original, ensuring backward compatibility.
Testing the Implementation
Run the test script to verify the implementation:
cd openhcs/processing/backends/analysis/
python test_simple_implementation.py
Test Coverage
# Test script covers:
# 1. Synthetic cell image generation
# 2. Full function testing
# 3. Simple function testing
# 4. Method comparison (VORONOI_OTSU vs THRESHOLD)
# 5. Memory efficiency testing
# 6. Visualization generation
def test_simple_implementation():
"""Test simplified cell counting implementation."""
# Create synthetic cell image
image = create_synthetic_cells(
num_cells=50,
image_size=(512, 512),
cell_radius=10
)
# Test full function
output, results, masks = count_cells_single_channel(
image[np.newaxis, ...], # Add Z dimension
detection_method=DetectionMethod.VORONOI_OTSU
)
# Verify results
assert results[0].cell_count > 0
assert len(results[0].cell_positions) == results[0].cell_count
# Test simple function
count, positions = count_cells_simple(image)
assert count > 0
assert len(positions) == count
Test outputs:
Console summary of detected cells
Comparison plots (VORONOI_OTSU vs THRESHOLD)
Memory usage statistics
Visualization of segmentation masks
Common Patterns
Basic Cell Counting
from openhcs.processing.backends.analysis.cell_counting_pyclesperanto_simple import (
count_cells_single_channel, DetectionMethod
)
# Load image stack (Z, Y, X format)
image_stack = load_image_stack('path/to/images')
# Count cells
output_stack, results, masks = count_cells_single_channel(
image_stack,
detection_method=DetectionMethod.VORONOI_OTSU,
gaussian_sigma=1.0,
min_cell_area=50,
max_cell_area=5000,
return_segmentation_mask=True
)
# Process results
for i, result in enumerate(results):
print(f"Image {i}: {result.cell_count} cells")
Quick Analysis
from openhcs.processing.backends.analysis.cell_counting_pyclesperanto_simple import count_cells_simple
# For 2D images - quick one-liner
cell_count, positions = count_cells_simple(
your_2d_image,
gaussian_sigma=1.0,
min_cell_area=50,
max_cell_area=5000
)
print(f"Detected {cell_count} cells")
print(f"Positions:\n{positions}")
Parameter Tuning
# Adjust gaussian_sigma for noise level
# - Higher sigma: More smoothing, fewer false positives
# - Lower sigma: Less smoothing, more sensitivity
# Low noise images
output, results, masks = count_cells_single_channel(
image_stack,
gaussian_sigma=0.5 # Less smoothing
)
# High noise images
output, results, masks = count_cells_single_channel(
image_stack,
gaussian_sigma=2.0 # More smoothing
)
# Adjust area filters for cell size
# - min_cell_area: Remove small noise
# - max_cell_area: Remove large artifacts
# Small cells (e.g., bacteria)
output, results, masks = count_cells_single_channel(
image_stack,
min_cell_area=10,
max_cell_area=500
)
# Large cells (e.g., neurons)
output, results, masks = count_cells_single_channel(
image_stack,
min_cell_area=200,
max_cell_area=10000
)
Migration from Original Implementation
The simplified implementation provides a straightforward migration path:
# Old approach (complex, 50+ parameters)
from openhcs.processing.backends.analysis.cell_counting_pyclesperanto import (
count_cells_single_channel
)
output_stack, results, masks = count_cells_single_channel(
image_stack,
detection_method=DetectionMethod.BLOB_LOG,
min_sigma=1.0,
max_sigma=10.0,
num_sigma=10,
threshold=0.1,
overlap=0.5,
# ... 45+ more parameters
)
# New approach (simple, 5 core parameters)
from openhcs.processing.backends.analysis.cell_counting_pyclesperanto_simple import (
count_cells_single_channel, DetectionMethod
)
output_stack, results, masks = count_cells_single_channel(
image_stack,
detection_method=DetectionMethod.VORONOI_OTSU,
gaussian_sigma=1.0,
min_cell_area=50,
max_cell_area=5000,
return_segmentation_mask=True
)
Migration benefits:
5x reduction in code complexity
Fewer parameters to tune
Better performance (less overhead)
Same materialization outputs
Full backward compatibility
Implementation Notes
🔬 Source Code:
Implementation:
openhcs/processing/backends/analysis/cell_counting_pyclesperanto_simple.py(line 1)Tests:
openhcs/processing/backends/analysis/test_simple_implementation.py(line 1)README:
openhcs/processing/backends/analysis/README_SIMPLE_IMPLEMENTATION.md(line 1)
🏗️ Architecture:
Analysis Consolidation System - Materialization system
Creating Custom Functions - Custom function integration
📊 Performance:
Code size: 334 lines (vs 1,578 lines original)
Memory usage: Automatic GPU cleanup (no manual management)
Processing speed: Faster (simplified workflow)
Parameter count: 5 core parameters (vs 50+ original)
Key Design Decisions
Why Voronoi-Otsu instead of blob detection?
Voronoi-Otsu is the pyclesperanto reference workflow, providing robust cell segmentation with minimal parameters. Blob detection requires extensive parameter tuning.
Why two functions (full vs simple)?
The full function maintains compatibility with existing pipelines. The simple function provides a quick API for interactive analysis.
Why automatic GPU memory management?
Pyclesperanto handles GPU memory automatically. Manual cleanup adds complexity without benefit.
Common Gotchas
Image format must be (Z, Y, X): Single 2D images need
image[np.newaxis, ...]to add Z dimensionGPU memory required: Pyclesperanto requires CUDA-capable GPU
Area filters are in pixels: Adjust based on image resolution and cell size
Gaussian sigma affects sensitivity: Higher sigma reduces false positives but may miss small cells
Debugging Cell Counting Issues
Symptom: Too Many False Positives
Cause: Noise or low gaussian_sigma
Diagnosis: Visualize segmentation masks
Fix: Increase gaussian_sigma or min_cell_area
# Increase smoothing
output, results, masks = count_cells_single_channel(
image_stack,
gaussian_sigma=2.0 # Increased from 1.0
)
# Or increase minimum area
output, results, masks = count_cells_single_channel(
image_stack,
min_cell_area=100 # Increased from 50
)
Symptom: Missing Cells
Cause: Over-smoothing or high min_cell_area
Diagnosis: Check cell sizes in original image
Fix: Decrease gaussian_sigma or min_cell_area
# Reduce smoothing
output, results, masks = count_cells_single_channel(
image_stack,
gaussian_sigma=0.5 # Decreased from 1.0
)
# Or decrease minimum area
output, results, masks = count_cells_single_channel(
image_stack,
min_cell_area=20 # Decreased from 50
)
Symptom: Touching Cells Not Separated
Cause: THRESHOLD method doesn’t separate touching cells
Diagnosis: Switch to VORONOI_OTSU
Fix: Use Voronoi-Otsu method
# Use Voronoi-Otsu for cell separation
output, results, masks = count_cells_single_channel(
image_stack,
detection_method=DetectionMethod.VORONOI_OTSU # Changed from THRESHOLD
)