def test_cdog(self): im_nuclei_stain_data = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_nuclei_stain.npz')) im_nuclei_stain = im_nuclei_stain_data['Easy1_nuclei_stain'] im_nuclei_fgnd_mask_data = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_nuclei_fgnd_mask.npz')) im_nuclei_fgnd_mask = im_nuclei_fgnd_mask_data[ 'Easy1_nuclei_fgnd_mask'] sigma_min = 10.0 / np.sqrt(2.0) sigma_max = 40.0 / np.sqrt(2.0) im_dog_max, im_sigma_max = cdog(im_nuclei_stain, im_nuclei_fgnd_mask, sigma_min, sigma_max) im_dog_max_gtruth_data = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_cdog_max.npz')) im_dog_max_gtruth = im_dog_max_gtruth_data['Easy1_cdog_max'] assert_array_almost_equal_neighborhood_lines(im_dog_max, im_dog_max_gtruth, decimal=4) compare_maxima(im_dog_max, im_dog_max_gtruth) im_sigma_max_gtruth_data = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_cdog_sigma_max.npz')) im_sigma_max_gtruth = im_sigma_max_gtruth_data['Easy1_cdog_sigma_max'] assert_array_almost_equal_neighborhood_lines(im_sigma_max, im_sigma_max_gtruth, decimal=4)
def test_cdog(self): im_nuclei_stain = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_nuclei_stain.npy')) im_nuclei_fgnd_mask = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_nuclei_fgnd_mask.npy')) sigma_min = 10.0 / np.sqrt(2.0) sigma_max = 40.0 / np.sqrt(2.0) im_dog_max, im_sigma_max = cdog(im_nuclei_stain, im_nuclei_fgnd_mask, sigma_min, sigma_max) im_dog_max_gtruth = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_cdog_max.npy')) np.testing.assert_array_almost_equal(im_dog_max.astype(np.float16), im_dog_max_gtruth, decimal=4) im_sigma_max_gtruth = np.load( os.path.join(TEST_DATA_DIR, 'Easy1_cdog_sigma_max.npy')) np.testing.assert_array_almost_equal(im_sigma_max.astype(np.float16), im_sigma_max_gtruth, decimal=4)
def detect_nuclei_kofahi(im_nuclei_stain, args): # segment nuclear foreground mask # (assumes nuclei are darker on a bright background) im_nuclei_fgnd_mask = im_nuclei_stain < args.foreground_threshold # smooth foreground mask with closing and opening im_nuclei_fgnd_mask = skimage.morphology.closing( im_nuclei_fgnd_mask, skimage.morphology.disk(3)) im_nuclei_fgnd_mask = skimage.morphology.opening( im_nuclei_fgnd_mask, skimage.morphology.disk(3)) im_nuclei_fgnd_mask = sp.ndimage.morphology.binary_fill_holes( im_nuclei_fgnd_mask) # run adaptive multi-scale LoG filter im_log_max, im_sigma_max = htk_shape_filters.cdog( im_nuclei_stain, im_nuclei_fgnd_mask, sigma_min=args.min_radius / np.sqrt(2), sigma_max=args.max_radius / np.sqrt(2) ) # apply local maximum clustering im_nuclei_seg_mask, seeds, maxima = htk_seg.nuclear.max_clustering( im_log_max, im_nuclei_fgnd_mask, args.local_max_search_radius) # split any objects with disconnected fragments im_nuclei_seg_mask = htk_seg.label.split(im_nuclei_seg_mask, conn=8) # filter out small objects im_nuclei_seg_mask = htk_seg.label.area_open( im_nuclei_seg_mask, args.min_nucleus_area).astype(np.int) return im_nuclei_seg_mask
def detect_nuclei_kofahi(im_nuclei_stain, args): # segment foreground (assumes nuclei are darker on a bright background) im_nuclei_fgnd_mask = sp.ndimage.morphology.binary_fill_holes( im_nuclei_stain < args.foreground_threshold) # run adaptive multi-scale LoG filter im_log_max, im_sigma_max = htk_shape_filters.cdog( im_nuclei_stain, im_nuclei_fgnd_mask, sigma_min=args.min_radius / np.sqrt(2), sigma_max=args.max_radius / np.sqrt(2)) # apply local maximum clustering im_nuclei_seg_mask, seeds, maxima = htk_seg.nuclear.max_clustering( im_log_max, im_nuclei_fgnd_mask, args.local_max_search_radius) # filter out small objects im_nuclei_seg_mask = htk_seg.label.area_open( im_nuclei_seg_mask, args.min_nucleus_area).astype(np.int) return im_nuclei_seg_mask
def detect_nuclei(im_input, min_radius=6, max_radius=10, display_result=False): # color normalization ref_mu_lab = (8.63234435, -0.11501964, 0.03868433) ref_std_lab = (0.57506023, 0.10403329, 0.01364062) im_nmzd = htk_cnorm.reinhard(im_input, ref_mu_lab, ref_std_lab) # color deconvolution w_est = htk_cdeconv.rgb_separate_stains_macenko_pca(im_nmzd, 255) nuclear_chid = htk_cdeconv.find_stain_index( htk_cdeconv.stain_color_map['hematoxylin'], w_est) im_nuclei_stain = htk_cdeconv.color_deconvolution(im_nmzd, w_est, 255).Stains[:, :, nuclear_chid] # segment nuclei foreground th = skimage.filters.threshold_li(im_nuclei_stain) * 0.8 # th = skimage.filters.threshold_otsu(im_nuclei_stain) im_fgnd_mask = im_nuclei_stain < th im_fgnd_mask = skimage.morphology.opening(im_fgnd_mask, skimage.morphology.disk(2)) im_fgnd_mask = skimage.morphology.closing(im_fgnd_mask, skimage.morphology.disk(1)) # detect nuclei im_dog, im_dog_sigma = htk_shape_filters.cdog( im_nuclei_stain, im_fgnd_mask, sigma_min=min_radius / np.sqrt(2), sigma_max=max_radius / np.sqrt(2)) nuclei_coord = skimage.feature.peak_local_max(im_dog, min_distance=min_radius / 2, threshold_rel=0.1) nuclei_coord = nuclei_coord[im_fgnd_mask[nuclei_coord[:, 0], nuclei_coord[:, 1]], :] nuclei_rad = np.array([ im_dog_sigma[nuclei_coord[i, 0], nuclei_coord[i, 1]] * np.sqrt(2) for i in range(nuclei_coord.shape[0]) ]) # display result if display_result: print 'Number of nuclei = ', nuclei_coord.shape[0] plt.figure(figsize=(30, 20)) plt.subplot(2, 2, 1) plt.imshow(im_input) plt.title('Input', fontsize=labelsize) plt.axis('off') plt.subplot(2, 2, 2) plt.imshow(im_nuclei_stain) plt.title('Deconv nuclei stain', fontsize=labelsize) plt.axis('off') plt.subplot(2, 2, 3) plt.imshow(im_fgnd_mask) plt.title('Foreground mask', fontsize=labelsize) plt.axis('off') plt.subplot(2, 2, 4) plt.imshow(im_nmzd) plt.plot(nuclei_coord[:, 1], nuclei_coord[:, 0], 'k+') for i in range(nuclei_coord.shape[0]): cx = nuclei_coord[i, 1] cy = nuclei_coord[i, 0] r = nuclei_rad[i] mcircle = mpatches.Circle((cx, cy), r, color='g', fill=False) plt.gca().add_patch(mcircle) plt.title('Nuclei detection', fontsize=labelsize) plt.axis('off') plt.tight_layout() return nuclei_coord, nuclei_rad
def detect_nuclei_kofahi(im_nuclei_stain, im_nuclei_fgnd_mask, min_radius, max_radius, min_nucleus_area, local_max_search_radius): """Performs a nuclear segmentation using kofahi's method. This method uses scale-adaptive multi-scale Laplacian-of-Gaussian filtering for blob enhancement and a local maximum clustering for segmentation. The original implementation described by Al-Kofahi et al. uses Laplacian of Gaussian but this function replaces it with Difference of Gaussian to improve speed. Parameters ---------- im_nuclei_stain : array_like A hematoxylin intensity image obtained from ColorDeconvolution. im_nuclei_fgnd_mask: array_like A binary mask of the nuclear foreground typically obtained by applying a threshold on the hematoxylin/nuclei stain image min_radius : float Minimum nuclear radius (used to set min sigma of the multiscale LoG filter) max_radius : float Maximum nuclear radius (used to set max sigma of the multiscale LoG filter) min_nucleus_area : int Minimum area that each nucleus should have local_max_search_radius : float Local max search radius used for detection seed points in nuclei Returns ------- im_nuclei_seg_mask : array_like A 2D array mask of the nuclei segmentation. References ---------- .. [#] Y. Al-Kofahi et al "Improved Automatic Detection and Segmentation of Cell Nuclei in Histopathology Images" in IEEE Transactions on Biomedical Engineering, Volume: 57, Issue: 4, doi: 10.1109/TBME.2009.2035102, April 2010. """ # smooth foreground mask with closing and opening im_nuclei_fgnd_mask = skimage.morphology.closing( im_nuclei_fgnd_mask, skimage.morphology.disk(3)) im_nuclei_fgnd_mask = skimage.morphology.opening( im_nuclei_fgnd_mask, skimage.morphology.disk(3)) im_nuclei_fgnd_mask = sp.ndimage.morphology.binary_fill_holes( im_nuclei_fgnd_mask) if not np.any(im_nuclei_fgnd_mask): return im_nuclei_fgnd_mask # run adaptive multi-scale LoG filter im_log_max, im_sigma_max = htk_shape_filters.cdog( im_nuclei_stain, im_nuclei_fgnd_mask, sigma_min=min_radius / np.sqrt(2), sigma_max=max_radius / np.sqrt(2)) # apply local maximum clustering im_nuclei_seg_mask, seeds, maxima = htk.segmentation.nuclear.max_clustering( im_log_max, im_nuclei_fgnd_mask, local_max_search_radius) if seeds is None: return im_nuclei_seg_mask # split any objects with disconnected fragments im_nuclei_seg_mask = htk.segmentation.label.split(im_nuclei_seg_mask, conn=8) # filter out small objects im_nuclei_seg_mask = htk.segmentation.label.area_open( im_nuclei_seg_mask, min_nucleus_area).astype(np.int) return im_nuclei_seg_mask