Connected-component labeling turns foreground pixels in a mask into numbered objects. In SciPy, scipy.ndimage.label() counts separated regions in a binary or numeric array before measurement, filtering, or bounding-box extraction.

The default connectivity in two dimensions uses edge-touching neighbors, so diagonally touching pixels stay separate unless a different structure is supplied. A structuring element from generate_binary_structure(2, 2) switches the same mask to 8-connected labeling when diagonals should belong to the same object.

The sample mask keeps two diagonal joins visible and prints bounding slices from find_objects(). Zero remains the background in the label matrix, and positive integers identify the component that each foreground pixel belongs to.

Steps to label image components with SciPy:

  1. Create a Python script named image_components_label.py with a small mask and both connectivity modes.
    image_components_label.py
    import numpy as np
    from scipy import ndimage
     
     
    mask = np.array(
        [
            [0, 1, 1, 0, 0, 0],
            [0, 0, 1, 0, 1, 0],
            [1, 1, 0, 0, 0, 1],
            [0, 0, 0, 1, 1, 1],
        ],
        dtype=bool,
    )
     
    labels, component_count = ndimage.label(mask)
    component_slices = ndimage.find_objects(labels)
     
    diagonal_structure = ndimage.generate_binary_structure(2, 2)
    diagonal_labels, diagonal_count = ndimage.label(
        mask,
        structure=diagonal_structure,
    )
     
    def print_grid(title, array):
        print(f"{title}:")
        for row in array.astype(int):
            print("  " + " ".join(str(value) for value in row))
     
     
    print_grid("mask", mask)
    print()
    print_grid("default labels", labels)
    print(f"component_count: {component_count}")
    print()
    print("component slices:")
    for label_id, slices in enumerate(component_slices, start=1):
        row_slice, col_slice = slices
        pixels = np.count_nonzero(labels[slices] == label_id)
        print(
            f"  label {label_id}: "
            f"rows {row_slice.start}:{row_slice.stop}, "
            f"cols {col_slice.start}:{col_slice.stop}, "
            f"pixels {pixels}"
        )
    print()
    print_grid("8-connected labels", diagonal_labels)
    print(f"8_connected_component_count: {diagonal_count}")

    ndimage.label() treats any nonzero input value as foreground. Convert a grayscale or probability mask to a Boolean mask first when intensity values should not affect object membership.

  2. Run the script to verify the default labels and bounding slices.
    $ python image_components_label.py
    mask:
      0 1 1 0 0 0
      0 0 1 0 1 0
      1 1 0 0 0 1
      0 0 0 1 1 1
    
    default labels:
      0 1 1 0 0 0
      0 0 1 0 2 0
      3 3 0 0 0 4
      0 0 0 4 4 4
    component_count: 4
    
    component slices:
      label 1: rows 0:2, cols 1:3, pixels 3
      label 2: rows 1:2, cols 4:5, pixels 1
      label 3: rows 2:3, cols 0:2, pixels 2
      label 4: rows 2:4, cols 3:6, pixels 4
    
    8-connected labels:
      0 1 1 0 0 0
      0 0 1 0 2 0
      1 1 0 0 0 2
      0 0 0 2 2 2
    8_connected_component_count: 2
  3. Use the returned label matrix and component count for default connectivity.
    labels, component_count = ndimage.label(mask)

    For a 2-D input, the default structure connects only edge-touching foreground pixels. Diagonal-only contact does not merge components.

  4. Slice each labeled component when later measurement needs a bounding box.
    component_slices = ndimage.find_objects(labels)

    find_objects() ignores label 0 and returns label 1 at list index 0, label 2 at list index 1, and so on.

  5. Use 8-connected structure when diagonal contact should merge objects.
    diagonal_structure = ndimage.generate_binary_structure(2, 2)
    diagonal_labels, diagonal_count = ndimage.label(
        mask,
        structure=diagonal_structure,
    )

    The sample mask drops from four components to two because the diagonal neighbors join under 8-connected labeling.

  6. Remove the demo script after adapting the labeling code.
    $ rm image_components_label.py