CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-connected-components-3d

High-performance connected components analysis for 2D and 3D multilabel images with support for 26, 18, and 6-connected neighborhoods.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

statistics.mddocs/

Statistics and Analysis

Comprehensive analysis functions for extracting detailed information about connected components, including geometric properties, component extraction, and efficient iteration over individual regions.

Capabilities

Component Statistics

Compute detailed statistics about each connected component including voxel counts, bounding boxes, and centroids with options for different output formats.

def statistics(
    out_labels: NDArray[Any],
    no_slice_conversion: bool = False,
) -> Union[StatisticsDict, StatisticsSlicesDict]:
    """
    Compute basic statistics on the regions in the image.

    Parameters:
    - out_labels: A numpy array of connected component labels
    - no_slice_conversion: If True, return bounding_boxes as numpy array instead of slice tuples

    Returns:
    - StatisticsDict: With bounding_boxes as list of slice tuples
    - StatisticsSlicesDict: With bounding_boxes as numpy array (if no_slice_conversion=True)
    
    Dictionary structure:
    {
        'voxel_counts': NDArray[np.uint32],     # Index is label, value is voxel count
        'bounding_boxes': NDArray[np.uint16] | list[tuple[slice, slice, slice]],
        'centroids': NDArray[np.float64],       # Shape (N+1, 3) for x,y,z coordinates
    }
    """

Usage examples:

import cc3d
import numpy as np

# Generate connected components
labels_in = np.random.randint(0, 10, (100, 100, 100))
labels_out = cc3d.connected_components(labels_in)

# Get statistics (default format with slice tuples)
stats = cc3d.statistics(labels_out)

# Access component information
num_components = len(stats['voxel_counts']) - 1  # Subtract 1 for background
print(f"Found {num_components} components")

# Get size of each component (skip background at index 0)
component_sizes = stats['voxel_counts'][1:]
print(f"Sizes: {component_sizes}")

# Get centroid coordinates
centroids = stats['centroids'][1:]  # Skip background
for i, centroid in enumerate(centroids):
    x, y, z = centroid
    print(f"Component {i+1} centroid: ({x:.2f}, {y:.2f}, {z:.2f})")

# Extract regions using bounding boxes
for label in range(1, len(stats['voxel_counts'])):
    bbox_slices = stats['bounding_boxes'][label]
    region = labels_out[bbox_slices]
    component_mask = (region == label)
    print(f"Component {label} bounding box shape: {region.shape}")

# Alternative format with numpy arrays for bounding boxes
stats_array = cc3d.statistics(labels_out, no_slice_conversion=True)
# bounding_boxes is now NDArray[np.uint16] with shape (N+1, 6)
# Structure: [xmin, xmax, ymin, ymax, zmin, zmax] for each component
bbox_array = stats_array['bounding_boxes']
for label in range(1, len(stats_array['voxel_counts'])):
    xmin, xmax, ymin, ymax, zmin, zmax = bbox_array[label]
    print(f"Component {label}: x=[{xmin}:{xmax}], y=[{ymin}:{ymax}], z=[{zmin}:{zmax}]")

Component Iteration

Efficiently iterate through individual connected components with options for binary or labeled output and memory-optimized processing.

def each(
    labels: NDArray[UnsignedIntegerT],
    binary: bool = False,
    in_place: bool = False,
) -> Iterator[tuple[int, NDArray[UnsignedIntegerT]]]:
    """
    Returns an iterator that extracts each label from a dense labeling.

    Parameters:
    - labels: Connected component labeled array
    - binary: Create binary images (True/False) instead of using original label values
    - in_place: Much faster but resulting images are read-only

    Returns:
    - Iterator yielding (label, image) tuples

    Yields:
    - label: int, the component label
    - image: NDArray, the extracted component (binary or labeled based on 'binary' parameter)
    """

Usage examples:

import cc3d
import numpy as np

# Create test data
labels_in = np.random.randint(0, 5, (50, 50, 50))
labels_out = cc3d.connected_components(labels_in)

# Iterate through components (memory-efficient, read-only)
for label, component_image in cc3d.each(labels_out, in_place=True):
    component_volume = np.sum(component_image > 0)
    print(f"Component {label}: {component_volume} voxels")
    
    # Process the component (read-only access)
    max_value = np.max(component_image)
    print(f"  Max value: {max_value}")

# Create binary masks for each component
for label, binary_mask in cc3d.each(labels_out, binary=True, in_place=False):
    # binary_mask contains only True/False values
    print(f"Component {label} binary mask sum: {np.sum(binary_mask)}")
    
    # Since in_place=False, we can modify the mask
    binary_mask[binary_mask] = 255  # Convert to grayscale
    
# Create labeled images for each component (mutable copies)
for label, labeled_image in cc3d.each(labels_out, binary=False, in_place=False):
    # labeled_image contains the original label values for this component
    # Can be modified since in_place=False
    labeled_image[labeled_image == label] = 1000  # Relabel component
    
# Compare performance approaches
# Fast but read-only (recommended for analysis)
for label, img in cc3d.each(labels_out, in_place=True):
    analyze_component(img)  # Read-only analysis function

# Slower but mutable (for modification)
for label, img in cc3d.each(labels_out, in_place=False):
    modify_component(img)  # Function that modifies the component

Low-Level Component Access

Direct access to component location data for advanced processing and custom rendering operations.

def runs(labels: NDArray[np.unsignedinteger]) -> dict[int, list[tuple[int, int]]]:
    """
    Returns a dictionary describing where each label is located.
    
    Parameters:
    - labels: Connected component labeled array
    
    Returns:
    - dict mapping label -> list of (start, end) run positions
    """

def draw(
    label: ArrayLike,
    runs: list[tuple[int, int]],
    image: NDArray[UnsignedIntegerT],
) -> NDArray[UnsignedIntegerT]:
    """
    Draws label onto the provided image according to runs.
    
    Parameters:
    - label: The label value to draw
    - runs: List of (start, end) positions from runs() function
    - image: Target image to draw into
    
    Returns:
    - Modified image with label drawn
    """

Usage examples:

import cc3d
import numpy as np

# Get component locations as run-length encoded data
labels_out = cc3d.connected_components(input_image)
component_runs = cc3d.runs(labels_out)

# Examine structure
for label, run_list in component_runs.items():
    if label == 0:  # Skip background
        continue
    print(f"Component {label} has {len(run_list)} runs")
    print(f"  First run: positions {run_list[0][0]} to {run_list[0][1]}")

# Reconstruct individual components using runs
empty_image = np.zeros_like(labels_out)
for component_label in range(1, 6):  # Components 1-5
    if component_label in component_runs:
        cc3d.draw(component_label, component_runs[component_label], empty_image)

# Create custom visualization by selectively drawing components
visualization = np.zeros_like(labels_out)
large_components = [label for label, runs in component_runs.items() 
                   if len(runs) > 100]  # Components with many runs
for label in large_components:
    cc3d.draw(255, component_runs[label], visualization)  # Draw in white

Install with Tessl CLI

npx tessl i tessl/pypi-connected-components-3d

docs

core-ccl.md

filtering.md

graphs.md

index.md

statistics.md

tile.json