Source code for histomicstk.segmentation.nuclear.max_clustering

import numpy as np

from ._max_clustering_cython import _max_clustering_cython


[docs] def max_clustering(im_response, im_fgnd_mask, r=10): """Local max clustering pixel aggregation for nuclear segmentation. Takes as input a constrained log or other filtered nuclear image, a binary nuclear mask, and a clustering radius. For each pixel in the nuclear mask, the local max is identified. A hierarchy of local maxima is defined, and the root nodes used to define the label image. Parameters ---------- im_response : array_like A filtered-smoothed image where the maxima correspond to nuclear center. Typically obtained by constrained-LoG filtering on a hematoxylin intensity image obtained from ColorDeconvolution. im_fgnd_mask : array_like A binary mask of type boolean where nuclei pixels have value 'True', and non-nuclear pixels have value 'False'. r : float A scalar defining the clustering radius. Default value = 10. Returns ------- im_label : array_like im_label image where positive values correspond to foreground pixels that share mutual sinks. seeds : array_like An N x 2 array defining the (x,y) coordinates of nuclei seeds. max_response : array_like An N x 1 array containing the maximum response value corresponding to 'seeds'. See Also -------- histomicstk.filters.shape.clog References ---------- .. [#] XW. Wu et al "The local maximum clustering method and its application in microarray gene expression data analysis," EURASIP J. Appl. Signal Processing, volume 2004, no.1, pp.53-63, 2004. .. [#] Y. Al-Kofahi et al "Improved Automatic Detection and Segmentation of Cell Nuclei in Histopathology Images" in IEEE Transactions on Biomedical Engineering,vol.57,no.4,pp.847-52, 2010. """ import skimage.measure # find local maxima of all foreground pixels mval, mind = _max_clustering_cython( im_response, im_fgnd_mask.astype(np.int32), r, ) # identify connected regions of local maxima and define their seeds im_label = skimage.measure.label(im_fgnd_mask & (im_response == mval)) if not np.any(im_label): return im_label, None, None # compute normalized response min_resp = im_response.min() max_resp = im_response.max() resp_range = max_resp - min_resp if resp_range == 0: return np.zeros_like(im_label), None, None im_response_nmzd = (im_response - min_resp) / resp_range # compute object properties obj_props = skimage.measure.regionprops(im_label, im_response_nmzd) obj_props = [prop for prop in obj_props if np.isfinite(prop.weighted_centroid).all()] num_labels = len(obj_props) if num_labels == 0: return im_label, None, None # extract object seeds seeds = np.array( [obj_props[i].weighted_centroid for i in range(num_labels)]) seeds = np.round(seeds).astype(int) # fix seeds outside the object region - happens for non-convex objects for i in range(num_labels): sy = seeds[i, 0] sx = seeds[i, 1] if im_label[sy, sx] == obj_props[i].label: continue # find object point with closest manhattan distance to center of mass pts = obj_props[i].coords ydist = np.abs(pts[:, 0] - sy) xdist = np.abs(pts[:, 1] - sx) seeds[i, :] = pts[np.argmin(xdist + ydist), :] assert im_label[seeds[i, 0], seeds[i, 1]] == obj_props[i].label # get seed responses max_response = im_response[seeds[:, 0], seeds[:, 1]] # set label of each foreground pixel to the label of its nearest peak im_label_flat = im_label.ravel() pind = np.flatnonzero(im_fgnd_mask) mind_flat = mind.ravel() im_label_flat[pind] = im_label_flat[mind_flat[pind]] # return return im_label, seeds, max_response