grains#

Find grains in an image.

class topostats.grains.Grains(image: numpy.typing.NDArray, filename: str, pixel_to_nm_scaling: float, unet_config: dict[str, str | int | float | tuple[int | None, int, int, int] | None] | None = None, threshold_method: str | None = None, otsu_threshold_multiplier: float | None = None, threshold_std_dev: dict | None = None, threshold_absolute: dict | None = None, absolute_area_threshold: dict | None = None, direction: str | None = None, smallest_grain_size_nm2: float | None = None, remove_edge_intersecting_grains: bool = True, classes_to_merge: list[tuple[int, int]] | None = None, vetting: dict | None = None)[source]#

Find grains in an image.

Parameters:
  • image (npt.NDArray) – 2-D Numpy array of image.

  • filename (str) – File being processed (used in logging).

  • pixel_to_nm_scaling (float) – Scaling of pixels to nanometres.

  • unet_config (dict[str, str | int | float | tuple[int | None, int, int, int] | None]) –

    Configuration for the UNet model. model_path: str

    Path to the UNet model.

    grain_crop_padding: int

    Padding to add to the bounding box of the grain before cropping.

    upper_norm_bound: float

    Upper bound for normalising the image.

    lower_norm_bound: float

    Lower bound for normalising the image.

  • threshold_method (str) – Method for determining thershold to mask values, default is ‘otsu’.

  • otsu_threshold_multiplier (float) – Factor by which the below threshold is to be scaled prior to masking.

  • threshold_std_dev (dict) – Dictionary of ‘below’ and ‘above’ factors by which standard deviation is multiplied to derive the threshold if threshold_method is ‘std_dev’.

  • threshold_absolute (dict) – Dictionary of absolute ‘below’ and ‘above’ thresholds for grain finding.

  • absolute_area_threshold (dict) – Dictionary of above and below grain’s area thresholds.

  • direction (str) – Direction for which grains are to be detected, valid values are ‘above’, ‘below’ and ‘both’.

  • smallest_grain_size_nm2 (float) – Whether or not to remove grains that intersect the edge of the image.

  • remove_edge_intersecting_grains (bool) – Direction for which grains are to be detected, valid values are ‘above’, ‘below’ and ‘both’.

  • classes_to_merge (list[tuple[int, int]] | None) – List of tuples of classes to merge.

  • vetting (dict | None) – Dictionary of vetting parameters.

Methods

area_thresholding(image, area_thresholds)

Remove objects larger and smaller than the specified thresholds.

assemble_grain_mask_tensor_from_crops(...)

Combine individual grain crops into a single grain mask tensor.

calc_minimum_grain_size(image)

Calculate the minimum grain size in pixels squared.

calculate_region_connection_regions(...)

Get a list of connection regions between two classes.

colour_regions(image, **kwargs)

Colour the regions.

convert_classes_to_nearby_classes(...[, ...])

Convert all but the largest regions of one class into another class provided the former touches the latter.

convert_classes_when_too_big_or_small(...)

Convert classes when they are too big or too small based on size thresholds.

find_grains()

Find grains.

flatten_multi_class_tensor(grain_mask_tensor)

Flatten a multi-class image tensor to a single binary mask.

get_bounding_boxes(direction)

Derive a list of bounding boxes for each region from the derived region_properties.

get_individual_grain_crops(grain_mask_tensor)

Get individual grain crops from an image tensor.

get_multi_class_grain_bounding_boxes(...)

Get the bounding boxes for each grain in a multi-class image tensor.

get_region_properties(image, **kwargs)

Extract the properties of each region.

improve_grain_segmentation_unet(filename, ...)

Use a UNet model to re-segment existing grains to improve their accuracy.

keep_largest_labelled_region(labelled_image)

Keep only the largest region in a labelled image.

keep_largest_labelled_region_classes(...)

Keep only the largest region in specific classes.

label_regions(image[, background])

Label regions.

merge_classes(grain_mask_tensor, ...)

Merge classes in a grain mask tensor and add them to the grain tensor.

remove_noise(image, **kwargs)

Remove noise which are objects smaller than the 'smallest_grain_size_nm2'.

remove_objects_too_small_to_process(image, ...)

Remove objects whose dimensions in pixels are too small to process.

remove_small_objects(image, **kwargs)

Remove small objects from the input image.

tidy_border(image, **kwargs)

Remove grains touching the border.

update_background_class(grain_mask_tensor)

Update the background class to reflect the other classes.

vet_class_connection_points(...)

Vet the number of connection points between regions in specific classes.

vet_class_sizes_single_grain(...)

Remove regions of particular classes based on size thresholds.

vet_grains(grain_mask_tensor, ...)

Vet grains in a grain mask tensor based on a variety of criteria.

vet_numbers_of_regions_single_grain(...)

Check if the number of regions of different classes for a single grain is within thresholds.

area_thresholding(image: numpy.typing.NDArray, area_thresholds: tuple) numpy.typing.NDArray[source]#

Remove objects larger and smaller than the specified thresholds.

Parameters:
  • image (npt.NDArray) – Image array where the background == 0 and grains are labelled as integers >0.

  • area_thresholds (tuple) – List of area thresholds (in nanometres squared, not pixels squared), first is the lower limit for size, second is the upper.

Returns:

Array with small and large objects removed.

Return type:

npt.NDArray

static assemble_grain_mask_tensor_from_crops(grain_mask_tensor_shape: tuple[int, int, int], grain_crops_and_bounding_boxes: list[dict[str, numpy.typing.NDArray]]) numpy.typing.NDArray[source]#

Combine individual grain crops into a single grain mask tensor.

Parameters:
  • grain_mask_tensor_shape (tuple) – Shape of the grain mask tensor.

  • grain_crops_and_bounding_boxes (list) – List of dictionaries containing the grain crops and bounding boxes. Structure: [{“grain_tensor”: npt.NDArray, “bounding_box”: tuple, “padding”: int}].

Returns:

3-D Numpy array of the grain mask tensor.

Return type:

npt.NDArray

calc_minimum_grain_size(image: numpy.typing.NDArray) float[source]#

Calculate the minimum grain size in pixels squared.

Very small objects are first removed via thresholding before calculating the below extreme.

Parameters:

image (npt.NDArray) – 2-D Numpy image from which to calculate the minimum grain size.

Returns:

Minimum grains size in pixels squared. If there are areas a value of -1 is returned.

Return type:

float

static calculate_region_connection_regions(grain_mask_tensor: numpy.typing.NDArray, classes: tuple[int, int]) tuple[int, numpy.typing.NDArray, dict[int, numpy.typing.NDArray.<class 'int'>]][source]#

Get a list of connection regions between two classes.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

  • classes (tuple[int, int]) – Tuple pair of classes to calculate the connection regions.

Returns:

  • int – Number of connection regions.

  • npt.NDArray – 2-D Numpy array of the intersection labels.

  • dict – Dictionary of connection points indexed by region label.

colour_regions(image: numpy.typing.NDArray, **kwargs) numpy.typing.NDArray[source]#

Colour the regions.

Parameters:
  • image (npt.NDArray) – 2-D array of labelled regions to be coloured.

  • **kwargs – Arguments passed to ‘skimage.color.label2rgb(**kwargs)’.

Returns:

Numpy array of image with objects coloured.

Return type:

np.array

static convert_classes_to_nearby_classes(grain_mask_tensor: numpy.typing.NDArray, classes_to_convert: list[tuple[int, int]] | None, class_touching_threshold: int = 1) numpy.typing.NDArray[source]#

Convert all but the largest regions of one class into another class provided the former touches the latter.

Specifically, it takes a list of tuples of two integers (dubbed class A and class B). For each class A, class B pair, it will find the largest region of class A and flag it to be ignored. Then for each non-largest region of class A, it will check if it touches any class B region (within the class_touching_threshold distance). If it does, it will convert the region to class B.

This is useful for situations where you want just one region of class A and the model has a habit of producing small regions of class A interspersed in the class B regions, which should be class B instead.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

  • classes_to_convert (list) – List of tuples of classes to convert. Structure is [(class_a, class_b)].

  • class_touching_threshold (int) – Number of dilation passes to do to determine class A connectivity with class B.

Returns:

3-D Numpy array of the grain mask tensor with classes converted.

Return type:

npt.NDArray

static convert_classes_when_too_big_or_small(grain_mask_tensor: numpy.typing.NDArray, pixel_to_nm_scaling: float, class_conversion_size_thresholds: list[tuple[tuple[int, int, int], tuple[int, int]]] | None) numpy.typing.NDArray[source]#

Convert classes when they are too big or too small based on size thresholds.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

  • pixel_to_nm_scaling (float) – Scaling of pixels to nanometres.

  • class_conversion_size_thresholds (list) – List of class conversion size thresholds. Structure is [(class_index, class_to_convert_to_if_to_small, class_to_convert_to_if_too_big), (lower_threshold, upper_threshold)].

Returns:

3-D Numpy array of the grain mask tensor with classes converted based on size thresholds.

Return type:

npt.NDArray

find_grains()[source]#

Find grains.

static flatten_multi_class_tensor(grain_mask_tensor: numpy.typing.NDArray) numpy.typing.NDArray[source]#

Flatten a multi-class image tensor to a single binary mask.

The returned tensor is of boolean type in case there are multiple hits in the same pixel. We dont want to have 2s, 3s etc because this would cause issues in labelling and cause erroneous grains within grains.

Parameters:

grain_mask_tensor (npt.NDArray) – Multi class grain mask tensor tensor of shape (N, N, C).

Returns:

Combined binary mask of all but the background class (:, :, 0).

Return type:

npt.NDArray

get_bounding_boxes(direction: str) dict[source]#

Derive a list of bounding boxes for each region from the derived region_properties.

Parameters:

direction (str) – Direction of threshold for which bounding boxes are being calculated.

Returns:

Dictionary of bounding boxes indexed by region area.

Return type:

dict

static get_individual_grain_crops(grain_mask_tensor: numpy.typing.NDArray, padding: int = 1) tuple[list[numpy.typing.NDArray], list[numpy.typing.NDArray], int][source]#

Get individual grain crops from an image tensor.

Fetches individual grain crops from an image tensor, but zeros any non-connected grains in the crop region. This is to ensure that other grains do not affect further processing steps.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of image tensor.

  • padding (int) – Padding to add to the bounding box of the grain before cropping. Default is 1.

Returns:

  • list[npt.NDArray] – List of individual grain crops.

  • list[npt.NDArray] – List of bounding boxes for each grain.

  • int – Padding used for the bounding boxes.

static get_multi_class_grain_bounding_boxes(grain_mask_tensor: numpy.typing.NDArray) dict[source]#

Get the bounding boxes for each grain in a multi-class image tensor.

Finds the bounding boxes for each grain in a multi-class image tensor. Grains can span multiple classes, so the bounding boxes are found for the combined binary mask of contiguous grains across all classes.

Parameters:

grain_mask_tensor (npt.NDArray) – 3-D Numpy array of grain mask tensor.

Returns:

Dictionary of bounding boxes indexed by grain number.

Return type:

dict

static get_region_properties(image: array, **kwargs) list[source]#

Extract the properties of each region.

Parameters:
  • image (np.array) – Numpy array representing image.

  • **kwargs – Arguments passed to ‘skimage.measure.regionprops(**kwargs)’.

Returns:

List of region property objects.

Return type:

list

static improve_grain_segmentation_unet(filename: str, direction: str, unet_config: dict[str, str | int | float | tuple[int | None, int, int, int] | None], image: numpy.typing.NDArray, labelled_grain_regions: numpy.typing.NDArray) tuple[numpy.typing.NDArray, numpy.typing.NDArray][source]#

Use a UNet model to re-segment existing grains to improve their accuracy.

Parameters:
  • filename (str) – File being processed (used in logging).

  • direction (str) – Direction of threshold for which bounding boxes are being calculated.

  • unet_config (dict[str, str | int | float | tuple[int | None, int, int, int] | None]) –

    Configuration for the UNet model. model_path: str

    Path to the UNet model.

    grain_crop_padding: int

    Padding to add to the bounding box of the grain before cropping.

    upper_norm_bound: float

    Upper bound for normalising the image.

    lower_norm_bound: float

    Lower bound for normalising the image.

  • image (npt.NDArray) – 2-D Numpy array of image.

  • labelled_grain_regions (npt.NDArray) – 2-D Numpy array of labelled grain regions.

Returns:

  • npt.NDArray – NxNxC Numpy array of the UNet mask.

  • npt.NDArray – NxNxC Numpy array of the labelled regions from the UNet mask.

static keep_largest_labelled_region(labelled_image: numpy.typing.NDArray.<class 'numpy.int32'>) numpy.typing.NDArray.<class 'numpy.bool_'>[source]#

Keep only the largest region in a labelled image.

Parameters:

labelled_image (npt.NDArray) – 2-D Numpy array of labelled regions.

Returns:

2-D Numpy boolean array of labelled regions with only the largest region.

Return type:

npt.NDArray

static keep_largest_labelled_region_classes(single_grain_mask_tensor: numpy.typing.NDArray, keep_largest_labelled_regions_classes: list[int] | None) numpy.typing.NDArray[source]#

Keep only the largest region in specific classes.

Parameters:
  • single_grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

  • keep_largest_labelled_regions_classes (list[int]) – List of classes to keep only the largest region.

Returns:

3-D Numpy array of the grain mask tensor with only the largest regions in specific classes.

Return type:

npt.NDArray

static label_regions(image: numpy.typing.NDArray, background: int = 0) numpy.typing.NDArray[source]#

Label regions.

This method is used twice, once prior to removal of small regions and again afterwards which is why an image must be supplied rather than using ‘self’.

Parameters:
  • image (npt.NDArray) – 2-D Numpy array of image.

  • background (int) – Value used to indicate background of image. Default = 0.

Returns:

2-D Numpy array of image with regions numbered.

Return type:

npt.NDArray

static merge_classes(grain_mask_tensor: numpy.typing.NDArray, classes_to_merge: list[tuple[int]] | None) numpy.typing.NDArray[source]#

Merge classes in a grain mask tensor and add them to the grain tensor.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

  • classes_to_merge (list | None) – List of tuples for classes to merge, can be any number of classes.

Returns:

3-D Numpy array of the grain mask tensor with classes merged.

Return type:

npt.NDArray

remove_noise(image: numpy.typing.NDArray, **kwargs) numpy.typing.NDArray[source]#

Remove noise which are objects smaller than the ‘smallest_grain_size_nm2’.

This ensures that the smallest objects ~1px are removed regardless of the size distribution of the grains.

Parameters:
  • image (npt.NDArray) – 2-D Numpy array to be cleaned.

  • **kwargs – Arguments passed to ‘skimage.morphology.remove_small_objects(**kwargs)’.

Returns:

2-D Numpy array of image with objects < smallest_grain_size_nm2 removed.

Return type:

npt.NDArray

remove_objects_too_small_to_process(image: numpy.typing.NDArray, minimum_size_px: int, minimum_bbox_size_px: int) numpy.typing.NDArray.<class 'numpy.bool_'>[source]#

Remove objects whose dimensions in pixels are too small to process.

Parameters:
  • image (npt.NDArray) – 2-D Numpy array of image.

  • minimum_size_px (int) – Minimum number of pixels for an object.

  • minimum_bbox_size_px (int) – Limit for the minimum dimension of an object in pixels. Eg: 5 means the object’s bounding box must be at least 5x5.

Returns:

2-D Numpy array of image with objects removed that are too small to process.

Return type:

npt.NDArray

remove_small_objects(image: array, **kwargs) numpy.typing.NDArray[source]#

Remove small objects from the input image.

Threshold determined by the minimum grain size, in pixels squared, of the classes initialisation.

Parameters:
  • image (np.array) – 2-D Numpy array to remove small objects from.

  • **kwargs – Arguments passed to ‘skimage.morphology.remove_small_objects(**kwargs)’.

Returns:

2-D Numpy array of image with objects < minimumm_grain_size removed.

Return type:

npt.NDArray

tidy_border(image: numpy.typing.NDArray, **kwargs) numpy.typing.NDArray[source]#

Remove grains touching the border.

Parameters:
  • image (npt.NDarray) – 2-D Numpy array representing the image.

  • **kwargs – Arguments passed to ‘skimage.segmentation.clear_border(**kwargs)’.

Returns:

2-D Numpy array of image without objects touching the border.

Return type:

npt.NDarray

static update_background_class(grain_mask_tensor: numpy.typing.NDArray) numpy.typing.NDArray[source]#

Update the background class to reflect the other classes.

Parameters:

grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

Returns:

3-D Numpy array of image tensor with updated background class.

Return type:

npt.NDArray

static vet_class_connection_points(grain_mask_tensor: numpy.typing.NDArray, class_connection_point_thresholds: list[tuple[tuple[int, int], tuple[int, int]]] | None) bool[source]#

Vet the number of connection points between regions in specific classes.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

  • class_connection_point_thresholds (list[tuple[tuple[int, int], tuple[int, int]]] | None) – List of tuples of classes and connection point thresholds. Structure is [(class_pair, (lower, upper))].

Returns:

True if the grain passes the vetting, False if it fails.

Return type:

bool

static vet_class_sizes_single_grain(single_grain_mask_tensor: numpy.typing.NDArray, pixel_to_nm_scaling: float, class_size_thresholds: list[tuple[int, int, int]] | None) tuple[numpy.typing.NDArray, bool][source]#

Remove regions of particular classes based on size thresholds.

Regions of classes that are too large or small may need to be removed for many reasons (eg removing noise erroneously detected by the model or larger-than-expected molecules that are obviously erroneous), this method allows for the removal of these regions based on size thresholds.

Parameters:
  • single_grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the mask tensor.

  • pixel_to_nm_scaling (float) – Scaling of pixels to nanometres.

  • class_size_thresholds (list[list[int, int, int]] | None) – List of class size thresholds. Structure is [(class_index, lower, upper)].

Returns:

  • npt.NDArray – 3-D Numpy array of the mask tensor with grains removed based on size thresholds.

  • bool – True if the grain passes the vetting, False if it fails.

static vet_grains(grain_mask_tensor: numpy.typing.NDArray, pixel_to_nm_scaling: float, class_conversion_size_thresholds: list[tuple[tuple[int, int, int], tuple[int, int]]] | None, class_size_thresholds: list[tuple[int, int, int]] | None, class_region_number_thresholds: list[tuple[int, int, int]] | None, nearby_conversion_classes_to_convert: list[tuple[int, int]] | None, class_touching_threshold: int, keep_largest_labelled_regions_classes: list[int] | None, class_connection_point_thresholds: list[tuple[tuple[int, int], tuple[int, int]]] | None) numpy.typing.NDArray[source]#

Vet grains in a grain mask tensor based on a variety of criteria.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor.

  • pixel_to_nm_scaling (float) – Scaling of pixels to nanometres.

  • class_conversion_size_thresholds (list) – List of class conversion size thresholds. Structure is [(class_index, class_to_convert_to_if_too_small, class_to_convert_to_if_too_big), (lower_threshold, upper_threshold)].

  • class_size_thresholds (list) – List of class size thresholds. Structure is [(class_index, lower, upper)].

  • class_region_number_thresholds (list) – List of class region number thresholds. Structure is [(class_index, lower, upper)].

  • nearby_conversion_classes_to_convert (list) – List of tuples of classes to convert. Structure is [(class_a, class_b)].

  • class_touching_threshold (int) – Number of dilation passes to do to determine class A connectivity with class B.

  • keep_largest_labelled_regions_classes (list) – List of classes to keep only the largest region.

  • class_connection_point_thresholds (list) – List of tuples of classes and connection point thresholds. Structure is [(class_pair, (lower, upper))].

Returns:

3-D Numpy array of the vetted grain mask tensor.

Return type:

npt.NDArray

static vet_numbers_of_regions_single_grain(grain_mask_tensor: numpy.typing.NDArray, class_region_number_thresholds: list[tuple[int, int, int]] | None) tuple[numpy.typing.NDArray, bool][source]#

Check if the number of regions of different classes for a single grain is within thresholds.

Parameters:
  • grain_mask_tensor (npt.NDArray) – 3-D Numpy array of the grain mask tensor, should be of only one grain.

  • class_region_number_thresholds (list[list[int, int, int]]) – List of class region number thresholds. Structure is [(class_index, lower, upper)].

Returns:

  • npt.NDArray – 3-D Numpy array of the grain mask tensor with grains removed based on region number thresholds.

  • bool – True if the grain passes the vetting, False if it fails.