Skip to content

Slicer

Module for slicing two-dimensional arrays to three-dimensional arrays of stacked masks.

calculate_region_properties(array, spacing)

Calculate the region properties on a segmented array.

The arrays can be either individual slices from a three-dimensional image or the full three-dimensional array itself. If the later then the resulting attribute area will be a "volume". By including the spacing argument, which should be the pixel_to_nm_scaling attribute of the AFMSlicer object the area/volume is in the actual units measured rather than pixels.

Parameters

array : npt.NDArray Array of labelled regions. spacing : float Pixel to nm scaling.

Returns

list[RegionProperties] A list of RegionProperties.

Source code in src/afmslicer/slicer.py
def calculate_region_properties(array: npt.NDArray[np.int32], spacing: float) -> Any:
    """
    Calculate the region properties on a segmented array.

    The arrays can be either individual slices from a three-dimensional image or the full three-dimensional array
    itself. If the later then the resulting attribute ``area`` will be a "volume". By including the ``spacing``
    argument, which should be the ``pixel_to_nm_scaling`` attribute of the ``AFMSlicer`` object the area/volume is
    in the actual units measured rather than pixels.

    Parameters
    ----------
    array : npt.NDArray
        Array of labelled regions.
    spacing : float
        Pixel to nm scaling.

    Returns
    -------
    list[RegionProperties]
        A list of ``RegionProperties``.
    """
    return regionprops(array.astype(np.int8), spacing=spacing)

mask_slices(stacked_array, slices=None, layers=None, min_height=None, max_height=None)

Convert a three-dimensional sliced array into masks based.

A three-dimensional array is converted to masked layers where each layer indicates whether a position in the two-dimensional cross-section is above the threshold for that layer. Thresholds are determined from the data itself if not explicitly provided.

Parameters

stacked_array : npt.NDArray Three-dimensional numpy array of image heights, each layer should be a copy of the original. slices : int, optional Number of slices to mask. Determined directly from data if not provided (i.e. depth of three-dimensional array). layers : npt.NDArray, optional Array of height thresholds for each slice. Determined directly from data if not provided. min_height : np.float64, optional Minimum height. Determined directly from data if not provided. max_height : np.float64, optional Maximum height. Determined directly from data if not provided.

Returns

npt.NDArray[bool] Three-dimensional array of masks.

Source code in src/afmslicer/slicer.py
def mask_slices(
    stacked_array: npt.NDArray[np.float64],
    slices: int | None = None,
    layers: npt.NDArray | None = None,
    min_height: np.float64 | None = None,
    max_height: np.float64 | None = None,
) -> npt.NDArray[bool]:
    """
    Convert a three-dimensional sliced array into masks based.

    A three-dimensional array is converted to masked layers where each layer indicates whether a position in the
    two-dimensional cross-section is above the threshold for that layer. Thresholds are determined from the data itself
    if not explicitly provided.

    Parameters
    ----------
    stacked_array : npt.NDArray
        Three-dimensional numpy array of image heights, each layer should be a copy of the original.
    slices : int, optional
        Number of slices to mask. Determined directly from data if not provided (i.e. depth of three-dimensional array).
    layers : npt.NDArray, optional
        Array of height thresholds for each slice. Determined directly from data if not provided.
    min_height : np.float64, optional
        Minimum height. Determined directly from data if not provided.
    max_height : np.float64, optional
        Maximum height. Determined directly from data if not provided.

    Returns
    -------
    npt.NDArray[bool]
        Three-dimensional array of masks.
    """
    slices = stacked_array.shape[2] if slices is None else slices
    min_height = np.min(stacked_array) if min_height is None else min_height
    max_height = np.max(stacked_array) if max_height is None else max_height
    layers = np.linspace(min_height, max_height, slices) if layers is None else layers
    sliced_mask = stacked_array.copy()
    for layer, height in enumerate(layers):
        sliced_mask[:, :, layer] = np.where(stacked_array[:, :, layer] > height, 1, 0)
    # We want to capture the maximum...
    sliced_mask[:, :, slices - 1] = np.where(
        stacked_array[:, :, slices - 1] >= max_height, 1, 0
    )
    return sliced_mask

mask_small_artefacts(labelled_array, properties, minimum_size)

Mask labelled features that are less than a specified size from two-dimensional array.

Parameters

labelled_array : npt.NDArray[np.int32] A three-dimensional array with labelled regions. properties : dict[int, Any] The properties of labelled objects for each layer. minimum_size : float Minimum size below which labelled regions are removed.

Returns

npt.NDArray[np.int32] Masked array with labelled regions smaller than minimum_size masked.

Source code in src/afmslicer/slicer.py
def mask_small_artefacts(
    labelled_array: npt.NDArray[np.int32],
    properties: dict[int, Any],
    minimum_size: float,
) -> npt.NDArray[np.int32]:
    """
    Mask labelled features that are less than a specified size from two-dimensional array.

    Parameters
    ----------
    labelled_array : npt.NDArray[np.int32]
        A three-dimensional array with labelled regions.
    properties : dict[int, Any]
        The properties of labelled objects for each layer.
    minimum_size : float
        Minimum size below which labelled regions are removed.

    Returns
    -------
    npt.NDArray[np.int32]
        Masked array with labelled regions smaller than ``minimum_size`` masked.
    """
    assert len(labelled_array.shape) == 2, (
        "The labelled array should only have two dimensions."
    )
    masked_array = np.ma.MaskedArray(
        labelled_array, mask=np.zeros_like(labelled_array, dtype=bool), copy=True
    )
    for region, prop in enumerate(properties, start=1):
        if prop.area <= np.float64(minimum_size):  # type: ignore[attr-defined]
            masked_array.mask = np.where(
                masked_array.data == region, True, masked_array.mask
            )
    return masked_array

mask_small_artefacts_all_layers(labelled_array, properties, minimum_size)

Mask labelled features that are less than a specified size from three-dimensional array.

Parameters

labelled_array : npt.NDArray[np.int32] A three-dimensional array with labelled regions. properties : dict[int, Any] The properties of labelled objects for each layer. minimum_size : float Minimum size below which labelled regions are removed.

Returns

npt.NDArray[np.int32] Masked array with labelled regions smaller than minimum_size masked.

Source code in src/afmslicer/slicer.py
def mask_small_artefacts_all_layers(
    labelled_array: npt.NDArray[np.int32],
    properties: list[Any],
    minimum_size: float,
):
    """
    Mask labelled features that are less than a specified size from three-dimensional array.

    Parameters
    ----------
    labelled_array : npt.NDArray[np.int32]
        A three-dimensional array with labelled regions.
    properties : dict[int, Any]
        The properties of labelled objects for each layer.
    minimum_size : float
        Minimum size below which labelled regions are removed.

    Returns
    -------
    npt.NDArray[np.int32]
        Masked array with labelled regions smaller than ``minimum_size`` masked.
    """
    assert labelled_array.shape[2] == len(properties), (
        f"The three-dimensional array has {labelled_array.shape[2]} layers which differs from the number of layer "
        f"properties {len(properties)}"
    )
    masked_array = np.ma.masked_all_like(labelled_array)
    # For each layer extract the area to a dictionary
    for layer in range(labelled_array.shape[2]):
        masked_array[:, :, layer] = mask_small_artefacts(
            labelled_array=labelled_array[:, :, layer],
            properties=properties[layer],
            minimum_size=minimum_size,
        )
    return masked_array

region_properties_by_slices(array, spacing)

Calculate region properties for each layer in a three-dimensinoal sliced array.

Parameters

array : npt.NDArray[np.int32] Three-dimensional sliced and labelled array. spacing : float Pixel to nanometer scaling.

Returns

dict[int, Any] Dictionary of regionprops calculated using skimage.

Source code in src/afmslicer/slicer.py
def region_properties_by_slices(
    array: npt.NDArray[np.int32], spacing: float
) -> list[Any]:
    """
    Calculate region properties for each layer in a three-dimensinoal sliced array.

    Parameters
    ----------
    array : npt.NDArray[np.int32]
        Three-dimensional sliced and labelled array.
    spacing : float
        Pixel to nanometer scaling.

    Returns
    -------
    dict[int, Any]
        Dictionary of ``regionprops`` calculated using skimage.
    """

    slice_properties = []
    for layer in range(array.shape[2]):
        slice_properties.append(
            regionprops(array[:, :, layer].astype(np.int32), spacing=spacing)
        )
    return slice_properties

segment(array, method='label', tidy_border=True, **kwargs)

Segment an array.

Parameters

array : npt.NDArray Two-dimensional numpy array to segment. method : str, optional Segmentation method, supports the label (default) and watershed methods implemented by Scikit Image. tidy_border : bool Whether to remove objects that straddle the border of the image. **kwargs : dict[str, Any], optional Additional arguments to pass for segmentation.

Returns

npt.NDArray Labelled array of objects.

Source code in src/afmslicer/slicer.py
def segment(
    array: npt.NDArray,
    method: str | None = "label",
    tidy_border: bool = True,
    **kwargs: dict[str, Any] | None,
) -> npt.NDArray:
    """
    Segment an array.

    Parameters
    ----------
    array : npt.NDArray
        Two-dimensional numpy array to segment.
    method : str, optional
        Segmentation method, supports the ``label`` (default) and ``watershed`` methods implemented by Scikit Image.
    tidy_border : bool
        Whether to remove objects that straddle the border of the image.
    **kwargs : dict[str, Any], optional
        Additional arguments to pass for segmentation.

    Returns
    -------
    npt.NDArray
        Labelled array of objects.
    """
    if method is None:
        method = "label"
        # logger.info("No segmentation method specified, defaulting to 'label'.")
    if tidy_border:
        array = clear_border(array)
        # logger.info("")
    segmenter = _get_segments(method)
    return segmenter(array, **kwargs)

segment_slices(array, method='label', tidy_border=False)

Segment individual layers of a three-dimensional numpy array.

Parameters

array : npt.NDArray[np.bool] Three-dimensional boolean array to be segmented. method : str Sgementation method to use. Currne options are label (default) and watershed. tidy_border : bool Whether to tidy the border.

Returns

npt.NDArray[np.bool] Three-dimensional array of labelled layers.

Source code in src/afmslicer/slicer.py
def segment_slices(
    array: npt.NDArray[np.bool], method: str | None = "label", tidy_border: bool = False
) -> npt.NDArray[np.bool]:
    """
    Segment individual layers of a three-dimensional numpy array.

    Parameters
    ----------
    array : npt.NDArray[np.bool]
        Three-dimensional boolean array to be segmented.
    method : str
        Sgementation method to use. Currne options are ``label`` (default) and ``watershed``.
    tidy_border : bool
        Whether to tidy the border.

    Returns
    -------
    npt.NDArray[np.bool]
        Three-dimensional array of labelled layers.
    """
    for layer in np.arange(array.shape[2]):
        array[:, :, layer] = segment(array[:, :, layer], method, tidy_border)
    return array

show_layers(array)

Helper function for debugging which shows individual layers of a three-dimensional numpy array.

Parameters

array : npt.NDArray Three-dimensional numpy array.

Source code in src/afmslicer/slicer.py
def show_layers(array: npt.NDArray) -> None:
    """
    Helper function for debugging which shows individual layers of a three-dimensional numpy array.

    Parameters
    ----------
    array : npt.NDArray
        Three-dimensional numpy array.
    """
    assert len(array.shape) == 3, f"Array is not 3D : {array.shape=}"
    for layer in np.arange(0, array.shape[-1]):
        print(f"\nLayer {layer} \n{array[...,layer]=}\n")  # noqa: T201

slicer(heights, slices)

Convert a two-dimensional array to a three-dimensional stacked array with copies of the original in each layer.

Parameters

heights : npt.NDArray[np.float64] Two-dimensional numpy array of heights. slices : int Number of slices to make.

Returns

npt.NDArray[np.float64] Expanded numpy array with the original two-dimensional array copied slices in the third dimension.

Examples

import numpy as np simple = np.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) slicer(heights=simple, slices=2) array([[[1, 1], [2, 2], [3, 3]],

[[4, 4], [5, 5], [6, 6]],

[[7, 7], [8, 8], [9, 9]]])

Source code in src/afmslicer/slicer.py
def slicer(heights: npt.NDArray[np.float64], slices: int) -> npt.NDArray[np.float64]:
    """
    Convert a two-dimensional array to a three-dimensional stacked array with copies of the original in each layer.

    Parameters
    ----------
    heights : npt.NDArray[np.float64]
        Two-dimensional numpy array of heights.
    slices : int
        Number of slices to make.

    Returns
    -------
    npt.NDArray[np.float64]
        Expanded numpy array with the original two-dimensional array copied ``slices`` in the third dimension.

    Examples
    --------
    >>> import numpy as np
    >>> simple = np.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    >>> slicer(heights=simple, slices=2)
    array([[[1, 1],
        [2, 2],
        [3, 3]],

       [[4, 4],
        [5, 5],
        [6, 6]],

       [[7, 7],
        [8, 8],
        [9, 9]]])
    """
    return np.repeat(
        heights[
            :,
            :,
            np.newaxis,
        ],
        repeats=slices,
        axis=2,
    )