""" functions to convert between watershed and skeleton segmented images"""
import numpy as np
from scipy import stats
from pycellfit.segmentation_transform_utils import pad_with, PAD_VALUE, fill_region
[docs]def skeleton_to_watershed(skeleton_image_array, region_value=0, boundary_value=255, keep_boundaries=False):
""" converts a segmented skeleton image (all regions are same value with region boundaries being a second value)
to a watershed segmented image (each region has a unique value and there are no boundary pixels, background
region has value of zero)
:param skeleton_image_array: 2D numpy array with pixel values of a skeleton segmented image
:type skeleton_image_array: np.ndarray
:param region_value: value of pixels in regions in skeleton_segmented images (default is 0)
:type region_value: float
:param boundary_value: value of boundary pixels of regions in skeleton_segmented images (default is 255)
:type boundary_value: float
:param keep_boundaries: if True, watershed image will keep boundaries in returned result
:type keep_boundaries: bool
:return: watershed_image_array
:rtype watershed_image_array: np.ndarray
"""
# throw error if skeleton_image_array is not np.ndarray
if not isinstance(skeleton_image_array, np.ndarray):
raise TypeError("skeleton_image_array must be of type numpy.ndarray")
# throw error if skeleton_image_array is not two dimensions
if np.ndim(skeleton_image_array) != 2:
raise ValueError("skeleton_image_array must be two-dimensional")
# throw error if array contains more than two values
if not (np.unique(skeleton_image_array).shape == (2,)):
raise ValueError("skeleton_image_array does not have only two values")
# throw error if array contains values other than region_value and boundary_value
if not (np.array_equal(np.unique(skeleton_image_array), [region_value, boundary_value]) or np.array_equal(np.unique(
skeleton_image_array), [region_value, boundary_value])):
raise ValueError("skeleton_image_array contains values other than region_value and boundary_value")
# copy input array (skeleton image)
watershed_image_array = np.copy(skeleton_image_array)
# fill each region with unique value
new_value = 1
with np.nditer(watershed_image_array, flags=['multi_index'], op_flags=['readwrite']) as iterator:
for old_value in iterator:
if new_value == boundary_value:
# make sure we don't label any regions with same value as boundary value
new_value += 1
# if old_value == boundary_value:
# don't touch boundary pixels
elif old_value == region_value:
# if pixel has not been touched, fill region
position = iterator.multi_index
fill_region(watershed_image_array, position, new_value)
new_value += 1
# determine pixel value of background
# potential background values are the values in the four corners of the array
potential_background_values = watershed_image_array[[0, 0, -1, -1], [0, -1, 0, -1]]
# we determine the background value to be the mode of the potential values
background_value = stats.mode(potential_background_values)[0][0]
# search for position of background value
search_results = np.where(watershed_image_array == background_value)
background_position = list(zip(search_results[0], search_results[1]))[0]
# fill background region with zeros
fill_region(watershed_image_array, background_position, 0)
new_watershed_image_array = np.copy(watershed_image_array)
if not keep_boundaries:
# remove boundaries between regions
while boundary_value in new_watershed_image_array:
with np.nditer(new_watershed_image_array, flags=['multi_index'], op_flags=['readwrite']) as iterator:
for pixel in iterator:
if pixel == 255:
position = iterator.multi_index
north = tuple(map(lambda i, j: i + j, position, (-1, 0)))
east = tuple(map(lambda i, j: i + j, position, (0, 1)))
northeast = tuple(map(lambda i, j: i + j, position, (-1, 1)))
if watershed_image_array[north] != boundary_value:
pixel[...] = watershed_image_array[north]
elif watershed_image_array[northeast] != boundary_value:
pixel[...] = watershed_image_array[northeast]
elif watershed_image_array[east] != boundary_value:
pixel[...] = watershed_image_array[east]
watershed_image_array = np.copy(new_watershed_image_array)
return new_watershed_image_array
[docs]def watershed_to_skeleton(watershed_image_array, region_value=0, boundary_value=255):
""" converts a watershed segmented image (no boundaries between regions and each region has a different pixel
value, background region has value of zero) to a skeleton segmented image (each region has the same pixel value
and are separated by boundaries of a second value)
:param watershed_image_array: 2D numpy array with pixel values of a watershed segmented image
:type watershed_image_array: numpy.ndarray
:param region_value: desired value of all regions in the output (skeleton segmented) array
:type region_value: float
:param boundary_value: desired value of boundary pixels in the output (skeleton segmented) array
:type boundary_value: float
:return: skeleton_image_array
:rtype skeleton_image_array: np.ndarray
"""
# create a one pixel wide padding around the edge of the image for easy iteration
padded_array = np.pad(watershed_image_array, 1, pad_with)
# print(padded_array.shape)
# initialize a new nparray to hold skeleton segmented image (mask)
skeleton_image_array = np.full(watershed_image_array.shape, region_value)
# if a pixel neighbors a pixel of a different value, consider it a boundary point and add it to the skeleton image
for row in range(1, padded_array.shape[0] - 1):
for col in range(1, padded_array.shape[1] - 1):
neighboring_values = [padded_array[row, col],
padded_array[row, col + 1],
padded_array[row + 1, col],
padded_array[row + 1, col + 1]]
# remove duplicates
neighboring_values = list(set(neighboring_values))
# remove padded value if in list of neighboring values
try:
neighboring_values.remove(PAD_VALUE)
except ValueError:
pass
if len(neighboring_values) >= 2:
skeleton_image_array[row - 1, col - 1] = boundary_value
# FIXME: right most column and bottom row have boundary_value when they should not be
# temporary fix: set values in last row and column to region_value
skeleton_image_array[-1, :] = region_value
skeleton_image_array[:, -1] = region_value
return skeleton_image_array