Skip to content

Segmenting data

The qim3d library provides a set of methods for data segmentation.

qim3d.segmentation.watershed

watershed(binary_volume, min_distance=5)

Performs watershed segmentation to separate touching objects in a binary volume.

This function converts a binary mask (foreground vs. background) into a labeled instance segmentation (where each object is assigned a unique integer ID). It is particularly useful for splitting fused particles, cells, or blobs that have been merged during thresholding. The algorithm uses a distance transform to identify markers (seeds) at the centers of objects and grows regions outward until they meet the boundaries of the original mask.

Parameters:

Name Type Description Default
binary_volume ndarray

The 3D binary input mask. Non-zero elements represent the objects to segment.

required
min_distance int

The minimum distance (in pixels) allowed between distinct object centers (peaks). Increasing this value prevents over-segmentation (splitting single objects), while decreasing it helps separate closely packed objects. Defaults to 5.

5

Returns:

Name Type Description
labeled_vol ndarray

A 3D integer array with the same shape as the input. Each segmented object is filled with a unique label ID (1, 2, 3...). Background is 0.

num_labels int

The total count of unique objects identified.

Raises:

Type Description
ValueError

If binary_volume contains more than 2 unique values (i.e., is not binary).

Example

import qim3d

vol = qim3d.examples.cement_128x128x128
binary_volume = qim3d.filters.gaussian(vol, sigma = 2)<60

fig1 = qim3d.viz.slices_grid(binary_volume, slice_axis=1, display_figure=True)
operations-watershed_before

labeled_volume, num_labels = qim3d.segmentation.watershed(binary_volume)

cmap = qim3d.viz.colormaps.segmentation(num_labels)
fig2 = qim3d.viz.slices_grid(labeled_volume, slice_axis=1, color_map=cmap, display_figure=True)
operations-watershed_after

Source code in qim3d/segmentation/_common_segmentation_methods.py
def watershed(binary_volume: np.ndarray, min_distance: int = 5) -> tuple[np.ndarray, int]:
    """
    Performs watershed segmentation to separate touching objects in a binary volume.

    This function converts a binary mask (foreground vs. background) into a labeled instance segmentation (where each object is assigned a unique integer ID). It is particularly useful for splitting fused particles, cells, or blobs that have been merged during thresholding. The algorithm uses a distance transform to identify markers (seeds) at the centers of objects and grows regions outward until they meet the boundaries of the original mask.

    Args:
        binary_volume (np.ndarray): The 3D binary input mask. Non-zero elements represent the objects to segment.
        min_distance (int, optional): The minimum distance (in pixels) allowed between distinct object centers (peaks). Increasing this value prevents over-segmentation (splitting single objects), while decreasing it helps separate closely packed objects. Defaults to 5.

    Returns:
        labeled_vol (np.ndarray):
            A 3D integer array with the same shape as the input. Each segmented object is filled with a unique label ID (1, 2, 3...). Background is 0.
        num_labels (int):
            The total count of unique objects identified.

    Raises:
        ValueError: If `binary_volume` contains more than 2 unique values (i.e., is not binary).

    Example:
        ```python
        import qim3d

        vol = qim3d.examples.cement_128x128x128
        binary_volume = qim3d.filters.gaussian(vol, sigma = 2)<60

        fig1 = qim3d.viz.slices_grid(binary_volume, slice_axis=1, display_figure=True)
        ```
        ![operations-watershed_before](../../assets/screenshots/operations-watershed_before.png)

        ```python
        labeled_volume, num_labels = qim3d.segmentation.watershed(binary_volume)

        cmap = qim3d.viz.colormaps.segmentation(num_labels)
        fig2 = qim3d.viz.slices_grid(labeled_volume, slice_axis=1, color_map=cmap, display_figure=True)
        ```
        ![operations-watershed_after](../../assets/screenshots/operations-watershed_after.png)
    """
    import scipy
    import skimage

    if len(np.unique(binary_volume)) > 2:
        raise ValueError(
            'binary_volume has to be binary volume - it must contain max 2 unique values.'
        )

    # Compute distance transform of binary volume
    distance = scipy.ndimage.distance_transform_edt(binary_volume)

    # Find peak coordinates in distance transform
    coords = skimage.feature.peak_local_max(
        distance, min_distance=min_distance, labels=binary_volume
    )

    # Create a mask with peak coordinates
    mask = np.zeros(distance.shape, dtype=bool)
    mask[tuple(coords.T)] = True

    # Label peaks
    markers, _ = scipy.ndimage.label(mask)

    # Apply watershed segmentation
    labeled_volume = skimage.segmentation.watershed(
        -distance, markers=markers, mask=binary_volume
    )

    # Extract number of objects found
    num_labels = len(np.unique(labeled_volume)) - 1
    log.info(f'Total number of objects found: {num_labels}')

    return labeled_volume, num_labels

qim3d.segmentation.connected_components

connected_components(volume, connectivity=1)

Identifies and labels discrete objects (connected components) in a binary volume.

This function groups adjacent non-zero voxels into distinct clusters, assigning a unique integer ID to each detected object. It is a fundamental tool for segmentation analysis, allowing you to count particles, filter noise (by removing small isolated spots), or extract specific regions of interest based on their size.

It returns a specialized ConnectedComponents object that provides convenient methods to:

  • Filter objects by size (voxel count).
  • Extract specific components or their bounding boxes.
  • Analyze the distribution of object sizes via histograms.

Parameters:

Name Type Description Default
volume ndarray

The binary input volume (boolean or integer) where non-zero values represent the foreground objects.

required
connectivity int

Defines which neighbors are considered "connected".

  • 1: Faces only (6-connectivity in 3D). Closest packing.
  • 2: Faces and edges (18-connectivity in 3D).
  • 3: Faces, edges, and corners (26-connectivity in 3D). Loose packing.
1

Returns:

Name Type Description
cc ConnectedComponents

A wrapper object containing the labeled volume. It supports methods like filter_by_size(), filter_by_largest(), and sizes_histogram().

Example
import qim3d

vol = qim3d.examples.cement_128x128x128
binary = qim3d.filters.gaussian(vol, sigma=2) < 60
cc = qim3d.segmentation.connected_components(binary)
color_map = qim3d.viz.colormaps.segmentation(len(cc), style='bright')
qim3d.viz.slicer(cc.labels, slice_axis=1, color_map=color_map)
Show the largest connected components
import qim3d

vol = qim3d.examples.cement_128x128x128
binary = qim3d.filters.gaussian(vol, sigma=2) < 60
cc = qim3d.segmentation.connected_components(binary)
filtered = cc.filter_by_largest(5)

color_map = qim3d.viz.colormaps.segmentation(len(cc), style='bright')
qim3d.viz.volumetric(filtered, color_map=color_map, constant_opacity=True)
Filter the connected components by size
import qim3d

vol = qim3d.examples.cement_128x128x128
binary = qim3d.filters.gaussian(vol, sigma=2) < 60
cc = qim3d.segmentation.connected_components(binary)

# Show a histogram of the distribution of label sizes
cc.sizes_histogram()

# Based on the histogram, choose a range of sizes
filtered = cc.filter_by_size(min_size=1e2, max_size=2e2)

color_map = qim3d.viz.colormaps.segmentation(len(cc), style='bright')
qim3d.viz.volumetric(filtered, color_map=color_map, constant_opacity=True)
Source code in qim3d/segmentation/_connected_components.py
def connected_components(volume: np.ndarray, connectivity: int = 1) -> ConnectedComponents:
    """
    Identifies and labels discrete objects (connected components) in a binary volume.

    This function groups adjacent non-zero voxels into distinct clusters, assigning a unique integer ID to each detected object. It is a fundamental tool for segmentation analysis, allowing you to count particles, filter noise (by removing small isolated spots), or extract specific regions of interest based on their size.

    It returns a specialized `ConnectedComponents` object that provides convenient methods to:

    * **Filter** objects by size (voxel count).
    * **Extract** specific components or their bounding boxes.
    * **Analyze** the distribution of object sizes via histograms.

    Args:
        volume (np.ndarray): The binary input volume (boolean or integer) where non-zero values represent the foreground objects.
        connectivity (int, optional): Defines which neighbors are considered "connected".

            * **1**: Faces only (6-connectivity in 3D). Closest packing.
            * **2**: Faces and edges (18-connectivity in 3D).
            * **3**: Faces, edges, and corners (26-connectivity in 3D). Loose packing.

    Returns:
        cc (ConnectedComponents):
            A wrapper object containing the labeled volume. It supports methods like `filter_by_size()`, `filter_by_largest()`, and `sizes_histogram()`.

    Example:
        ```python
        import qim3d

        vol = qim3d.examples.cement_128x128x128
        binary = qim3d.filters.gaussian(vol, sigma=2) < 60
        cc = qim3d.segmentation.connected_components(binary)
        color_map = qim3d.viz.colormaps.segmentation(len(cc), style='bright')
        qim3d.viz.slicer(cc.labels, slice_axis=1, color_map=color_map)
        ```

    Example: Show the largest connected components
        ```python
        import qim3d

        vol = qim3d.examples.cement_128x128x128
        binary = qim3d.filters.gaussian(vol, sigma=2) < 60
        cc = qim3d.segmentation.connected_components(binary)
        filtered = cc.filter_by_largest(5)

        color_map = qim3d.viz.colormaps.segmentation(len(cc), style='bright')
        qim3d.viz.volumetric(filtered, color_map=color_map, constant_opacity=True)
        ```

    Example: Filter the connected components by size
        ```python
        import qim3d

        vol = qim3d.examples.cement_128x128x128
        binary = qim3d.filters.gaussian(vol, sigma=2) < 60
        cc = qim3d.segmentation.connected_components(binary)

        # Show a histogram of the distribution of label sizes
        cc.sizes_histogram()

        # Based on the histogram, choose a range of sizes
        filtered = cc.filter_by_size(min_size=1e2, max_size=2e2)

        color_map = qim3d.viz.colormaps.segmentation(len(cc), style='bright')
        qim3d.viz.volumetric(filtered, color_map=color_map, constant_opacity=True)
        ```
    """
    cc = ConnectedComponents(volume, connectivity)
    return cc