Source code for napari_toska._labeled_skeletonization

import numpy as np

_2D_NEIGHBORHOODS = ["n4", "n8"]
_3D_NEIGHBORHOODS = ["n6", "n18", "n26"]

[docs] def generate_labeled_skeletonization(label_image:'napari.types.LabelsData')->'napari.types.LabelsData': ''' Skeletonize a label image and relabels the skeleton to match input labels. The used skeletonization algorithm is the scikit-image implementation of Lee, 94. Parameters: ----------- label_image: napari.types.LabelsData Input napari version of numpy.ndarray containing integer labels of an instance segmentation. Returns: -------- labeled_skeletons: napari.types.LabelsData A skeleton image multiplied by the respective label of the object of origin in the input label image. ''' from skimage.morphology import skeletonize binary_skeleton = skeletonize(label_image.astype(bool)).astype(int) binary_skeleton = binary_skeleton / np.amax(binary_skeleton) labeled_skeletons = binary_skeleton * label_image return labeled_skeletons.astype(int)
[docs] def parse_all_skeletons(skeleton_image: "napari.types.LabelsData", neighborhood: str = "n4") -> "napari.types.LabelsData": from skimage import measure properties = measure.regionprops(skeleton_image) parsed_skeletons_assembly = np.zeros_like(skeleton_image) for prop in properties: sub_image = prop["image"] sub_bbox = prop["bbox"] sub_label = prop["label"] # pad subimages to avoid kernel failure on edges padded_sub_skeleton = np.pad(sub_image, 1) parsed_sub_skeleton = parse_single_skeleton(padded_sub_skeleton * sub_label, sub_label, neighborhood) # remove padding for 2D or 3D image and add to assembly if len(skeleton_image.shape) == 2: unpadded_image = parsed_sub_skeleton[1: -1, 1: -1] y0, x0, y1, x1 = sub_bbox parsed_skeletons_assembly[y0: y1, x0: x1] = unpadded_image elif len(skeleton_image.shape) == 3: unpadded_image = parsed_sub_skeleton[1: -1, 1: -1, 1: -1] z0, y0, x0, z1, y1, x1 = sub_bbox parsed_skeletons_assembly[z0: z1, y0: y1, x0: x1] = unpadded_image return parsed_skeletons_assembly
[docs] def parse_single_skeleton(skel: "napari.types.LabelsData", label: int, neighborhood: str = "n4") -> "napari.types.LabelsData": """ Label the skeleton of a 2D or 3D object with 4-, 6-, 8-, 18-, or 26-connectivity. Parameters ---------- skel : napari.types.LabelsData A skeletonized image label : int The label of the object whose skeleton is to be labeled. neighborhood : str The neighborhood connectivity of the skeleton. Can be "n4", "n6", "n8", "n18", or "n26". Returns ------- skeleton_labels : napari.types.LabelsData A labeled image with the same shape as `skel` where each pixel is labeled as either an end point (1), a branch point (2), or a skeleton pixel (3). """ from ._backend_toska_functions import (n4_parse_skel_2d, n8_parse_skel_2d, n6_parse_skel_3d, n18_parse_skel_3d, n26_parse_skel_3d) # retrieve chosen skeleton from image skel = skel == label if len(skel.shape) == 2: x_dir = 1 y_dir = 0 if neighborhood == "n4": _, e_pts, b_pts, brnch, _, _ = n4_parse_skel_2d(skel, y_dir, x_dir) elif neighborhood == "n8": _, e_pts, b_pts, brnch, _, _ = n8_parse_skel_2d(skel, y_dir, x_dir) elif len(skel.shape) == 3: x_dir = 2 y_dir = 1 z_dir = 0 if neighborhood == "n6": _, e_pts, b_pts, brnch, _, _ = n6_parse_skel_3d(skel, z_dir, y_dir, x_dir) elif neighborhood == "n18": _, e_pts, b_pts, brnch, _, _ = n18_parse_skel_3d(skel, z_dir, y_dir, x_dir) elif neighborhood == "n26": _, e_pts, b_pts, brnch, _, _ = n26_parse_skel_3d(skel, z_dir, y_dir, x_dir) skeleton_labels = np.zeros_like(skel, dtype=int) skeleton_labels[brnch > 0] = 2 if len(skel.shape) == 2: for pt in b_pts: skeleton_labels[pt[0], pt[1]] = 3 for pt in e_pts: skeleton_labels[pt[0], pt[1]] = 1 elif len(skel.shape) == 3: for pt in b_pts: skeleton_labels[pt[0], pt[1], pt[2]] = 3 for pt in e_pts: skeleton_labels[pt[0], pt[1], pt[2]] = 1 return skeleton_labels
[docs] def label_branches(parsed_skeletons: "napari.types.LabelsData", labelled_skeletons: "napari.types.LabelsData", neighborhood: str ) -> "napari.types.LabelsData": """ Label the branches of a skeleton image. The branch labels start with 1 and increase for each branch. Parameters ---------- parsed_skeletons : napari.types.LabelsData A skeleton image where each pixel is labelled according to the point type which can be either a terminal point (1), a branching point (3), or a chain point (2). labelled_skeletons : napari.types.LabelsData A skeleton image with each skeleton carrying a unique label. Returns ------- branch_label_image : napari.types.LabelsData A skeleton image where each branch is labelled with a skeleton-wise unique label. The branch labels of each skeleton start with 1 and increase for each branch up to the number of branches in the skeleton. """ import numpy as np from scipy import ndimage from ._utils import get_neighborhood structure = get_neighborhood(neighborhood) branch_image = parsed_skeletons == 2 skeletons_ids = np.arange(1, np.max(labelled_skeletons) + 1) branch_label_image = np.zeros_like(labelled_skeletons, dtype=int) for i in skeletons_ids: sub_skeleton_branches = branch_image * (labelled_skeletons == i) branch_label_image = ndimage.label( sub_skeleton_branches, structure=structure)[0] + branch_label_image return branch_label_image
[docs] def generate_labeled_outline(labels: 'napari.types.LabelsData'): """ Generate a labeled outline of a label image. Parameters ---------- labels : napari.types.LabelsData A label image. Returns ------- outline : napari.types.LabelsData A labeled outline of the label image. """ from skimage import segmentation, morphology outline = segmentation.find_boundaries( labels > 0, connectivity=labels.ndim, mode='inner') outline = morphology.skeletonize(outline) return outline * labels
[docs] def generate_local_thickness_skeleton( outline: 'napari.types.LabelsData', labeled_skeleton: 'napari.types.LabelsData' ) -> 'napari.types.ImageData': """ Generate a local thickness image of a skeleton. Parameters ---------- outline : napari.types.LabelsData A labeled outline of a label image. labeled_skeleton : napari.types.LabelsData A labeled skeleton image. Returns ------- thickness_image : napari.types.ImageData A local thickness image of the skeleton. """ from scipy.spatial.distance import cdist skeleton_coordinates = np.stack(np.where(labeled_skeleton > 0)).T boundary_coordinates = np.stack(np.where(outline > 0)).T distances_images = np.zeros_like(labeled_skeleton, dtype=np.float32) for point in skeleton_coordinates: distance = min(cdist([point], boundary_coordinates).squeeze()) distances_images[point[0], point[1]] = distance return distances_images
[docs] def reconstruct_from_local_thickness( thickness_image: 'napari.types.ImageData' ) -> 'napari.types.LabelsData': """ Reconstruct a label image from a local thickness image. Parameters ---------- thickness_image : napari.types.ImageData A local thickness image of a skeleton. Returns ------- reconstruction : napari.types.LabelsData A label image reconstructed from the local thickness image. """ from skimage import draw reconstruction = np.zeros_like(thickness_image, dtype=int) coordinates_skeleton = np.stack(np.where(thickness_image > 0)).T for pt in coordinates_skeleton: thickness = thickness_image[tuple(pt)] if thickness_image.ndim == 3: sphere = np.where(draw.ellipsoid(thickness, thickness, thickness) > 0) # add offset to coordinates sphere = np.stack([sphere[0] + pt[0], sphere[1] + pt[1], sphere[2] + pt[2]], axis=1) # overwrite values at coordinates reconstruction[sphere[:, 0], sphere[:, 1], sphere[:, 2]] = 1 elif thickness_image.ndim == 2: disk = draw.disk(pt, thickness, shape=reconstruction.shape) reconstruction[disk] = 1 return reconstruction