Clinical and pathologic correlations of machine learning quantification of Aβ deposits across 3 brain regions of decedents with Alzheimer disease.
The 1 match
- [1] § METHODS › Statistical analysis ↔ notebook/3) Analysis - Tissue Separation.ipynb, lines 479–551 · score 0.51 · standard deviations, computation, square, mixed, binary, zero
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 1,223 lines · 46 KB · GPL-3.0 · 1 match
- # %% [markdown]
- # ### 3 Analysis - Tissue Separation
- #
- # A sliding window approach was applied on the whole slide images (WSIs) with the trained CNN model to generate confidence heatmaps.
- #
- # The WSIs were tiled into 1536 x 1536 images patches in the prepocessing steps. A sliding window approached was applied on evey image patch of a WSI. At each time, the CNN model took a 256x256 pixels region as input, forward propagated and generated a prediction score for cored plaque, diffuse plaque and CAA respectively. By systematically sliding the input region across the entire 1536 x 1536 image patch, the prediction scores were saved and ploted as prediction confidence heatmap for this patch. The heatmap for the WSI was obtained by doing this on all image patches of it.
- #
- # Based on the heatmap prediction scores (saved as npy), we analyzed the plaque density distribution
- # %%
- import os, glob, datetime
- from time import time
- from tqdm import tqdm
- import numpy as np
- import matplotlib
- import matplotlib.pyplot as plt
- from PIL import Image
- Image.MAX_IMAGE_PIXELS = None
- import cppyy
- cppyy.load_library('gSLICr/build/libgSLICr_lib.so')
- cppyy.load_library('/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcudart.so')
- cppyy.add_include_path('/usr/local/cuda-10.0/targets/x86_64-linux/include') # Add cuda library
- cppyy.include('gSLICr/gSLICr_Lib/gSLICr.h')
- from cppyy.gbl import gSLICr
- import cppyy.ll
- from skimage.segmentation import find_boundaries, mark_boundaries
- from skimage.measure import regionprops, label
- from skimage.morphology import binary_dilation, convex_hull_image
- # %%
- IMG_DIR = 'data/outputs/thres_tissue_png/'
- SAVE_DIR = 'data/outputs/tissue_mask_png/'
- filenames = glob.glob(IMG_DIR + '*.png')
- filenames = [filename.split('/')[-1] for filename in filenames]
- print(filenames)
- # %% [markdown]
- # ### Final Pipeline
- # %%
- # Separate stains
- #from skimage.color import separate_stains # Too much memory burden, separate computation tasks
- from skimage.util import img_as_float
- from tempfile import mkdtemp
- import shutil
- def color_deconv(rgb, conv_matrix):
- """RGB to stain color space conversion using color deconvolution.
- Original implementation in scikit-image
- https://github.com/scikit-image/scikit-image/blob/81363d32b94763150125dcf2c8909533c37567d9/skimage/color/colorconv.py#L1364
- Parameters
- ----------
- rgb : array_like
- The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
- conv_matrix: ndarray
- The stain separation matrix as described by G. Landini [1]_.
- Returns
- -------
- out : ndarray
- The image in stain color channel, in a 2-D array of shape
- ``(.., ..)``.
- """
- #rgb = img_as_float(rgb, force_copy=True)
- rgb = rgb.astype(dtype='float32')
- rgb += 2
- rgb = -np.log10(rgb, dtype='float32')
- out_shape = (rgb.shape[0], rgb.shape[1], 2)
- #rgb = np.reshape(rgb, (-1, 3)) @ (conv_matrix[:,0:2]) # 0:2 because only H&E channels are needed
- rgb = np.matmul(np.reshape(rgb, (-1, 3)), (conv_matrix[:,0:2]), dtype='float32')
- return np.reshape(rgb, out_shape)
- # Scale [0,1] to [0,255]
- def uint8scale(arr, _min=0, _max=1):
- return ((arr - _min) * 1/(_max - _min) * 255).astype('uint8')
- # Scale arr to [new_min, new_max]
- def scale_range(arr, new_min, new_max):
- return (arr - arr.min()) * (new_max - new_min) / (arr.max() - arr.min()) + new_min
- # Scale arr to have new_mean and new_std
- def scale_meanstd(arr, new_mean, new_std):
- return (arr - np.mean(arr)) / np.std(arr) * new_std + new_mean
- # Apply contrast threshold using QuPath data
- def apply_contrast_threshold(arr, thres_min, thres_max):
- arr[arr < thres_min] = thres_min
- arr[arr > thres_max] = thres_max
- return scale_range(arr, 0, 1)
- # Create a memmap numpy array on disk for float64 WSI
- # dirpath = mkdtemp()
- # fp = os.path.join(dirpath, 'arr.dat')
- # print(dirpath)
- startT = time()
- # Divide the WSI into regular slices to avoid insufficient memory
- # Cannot divide WSI into slices due to contrast threshold and scale_meanstd
- # slice_size = 10000
- rgb_img = np.array(Image.open('data/outputs/norm_png/NA4077-02_AB.png'))
- height, width, _ = rgb_img.shape
- print(rgb_img.shape)
- # Corresponding stain matrix
- #stain_mat = np.array([[0.572, 0.589, 0.572], [0.281, 0.514, 0.811], [0.488, -0.804, 0.341]]) # NA3077
- stain_mat = np.array([[0.624, 0.662, 0.415], [0.469, 0.612, 0.637], [0.616, -0.743, 0.261]]) # ref: NA5002_2
- stain_mat = np.linalg.inv(stain_mat)
- # Corresponding scale and contrast threshold constants
- consts = []
- consts.append({'mean':0.037, 'std':0.12, 'thres_min':-0.045568, 'thres_max':1.051931})
- consts.append({'mean':0.091, 'std':0.106, 'thres_min':0.01, 'thres_max':0.3})
- # t = tqdm(total=2)
- # for i in range(2):
- # Separate stains
- # i=0: Hematoxylin, i=1: Eosin, i=2: Residual
- print('Load Image: %s' % (time()-startT))
- mask_img = color_deconv(rgb_img, stain_mat)
- del rgb_img
- print('Color Deconv: %s' % (time()-startT))
- norm_img = []
- for i in range(2):
- # Scale using mean and standard deviation
- mask_img[:,:,i] = scale_meanstd(mask_img[:,:,i], consts[i]['mean'], consts[i]['std'])
- # Apply contrast threshold
- mask_img[:,:,i] = apply_contrast_threshold(mask_img[:,:,i], consts[i]['thres_min'], consts[i]['thres_max'])
- # Convert into uint8
- norm_img.append(np.asarray(uint8scale(mask_img[:,:,i]), dtype='uint8'))
- del mask_img
- # Convert numpy array into PIL image and save to local
- # Divide the WSI into regular slices to avoid PIL buffer overflow
- slice_size = 30000
- iters = np.uint8(np.ceil([height / slice_size, width / slice_size]))
- for i in range(2):
- mask_img = Image.new('L', (width, height))
- for row in range(iters[0]):
- for col in range(iters[1]):
- # Get start and end pixel location
- start_r, start_c = row * slice_size, col * slice_size
- end_r, end_c = min(height, start_r + slice_size), min(width, start_c + slice_size)
- # Paste the slice mask to image mask
- mask_slice = Image.fromarray(norm_img[i][start_r:end_r, start_c:end_c], 'L')
- mask_img.paste(mask_slice, (start_c, start_r))
- # Save masked image
- if i == 0:
- mask_img.save('data/outputs/norm_png/NA4077-02_AB_Hemato_4.png')
- elif i == 1:
- mask_img.save('data/outputs/norm_png/NA4077-02_AB_Eosin_4.png')
- del mask_slice, mask_img
- print('Total: %s' % (time()-startT))
- # %%
- # Function definition for each step
- # Helper func: Scale [0,1] to [0,255]
- def uint8scale(arr, _min=0, _max=1):
- """Scale an array to [0,255].
- Parameters
- -----------
- arr : ndarray
- Input array.
- _min : int, optional
- Minimum value within arr.
- _max : int, optional
- Maximum value within arr.
- Returns
- -------
- output : ndarray
- The scaled array.
- """
- return ((arr - _min) * 1/(_max - _min) * 255).astype('uint8')
- def run_slic_seg(img, n_segments=80000, n_iter=5, compactness=0.01, enforce_connectivity=True, slic_zero=True):
- """Run gpu SLICO in C++.
- Parameters
- -----------
- img : 2D ndarray
- Input image, must be grayscale.
- n_segments : int, optional
- The (approximate) number of labels in the segmented output image.
- n_iter : int, optional
- The number of iterations of k-means.
- compactness : float, optional
- Balances color proximity and space proximity. Higher values give
- more weight to space proximity, making superpixel shapes more
- square/cubic. In SLICO mode, this is the initial compactness.
- This parameter depends strongly on image contrast and on the
- shapes of objects in the image. We recommend exploring possible
- values on a log scale, e.g., 0.01, 0.1, 1, 10, 100, before
- refining around a chosen value.
- enforce_connectivity : bool, optional
- Whether the generated segments are connected or not.
- slic_zero : bool, optional
- Run SLIC-zero, the zero-parameter mode of SLIC. [2]_
- Returns
- -------
- segments_slic : 2D ndarray
- Integer mask indicating SLIC segment labels.
- References
- ----------
- .. [1] Radhakrishna Achanta, Appu Shaji, Kevin Smith, Aurelien Lucchi,
- Pascal Fua, and Sabine Süsstrunk, SLIC Superpixels Compared to
- State-of-the-art Superpixel Methods, TPAMI, May 2012.
- .. [2] http://ivrg.epfl.ch/research/superpixels#SLICO
- """
- height, width = img.shape
- # Create a Run_SLIC C++ class
- run_slic = gSLICr.Run_SLIC()
- # Specify settings
- gslic_settings = run_slic.get_settings()
- gslic_settings = cppyy.bind_object(gslic_settings, gSLICr.objects.settings)
- gslic_settings.img_size.x = width
- gslic_settings.img_size.y = height
- gslic_settings.no_segs = n_segments
- gslic_settings.no_iters = n_iter
- gslic_settings.coh_weight = compactness
- gslic_settings.do_enforce_connectivity = enforce_connectivity
- gslic_settings.slic_zero = slic_zero
- gslic_settings.color_space = gSLICr.GRAY
- gslic_settings.seg_method = gSLICr.GIVEN_NUM
- run_slic.print_settings()
- # Start running gSLIC
- run_slic.run(img)
- # Get the segmentation mask
- segments_slic = run_slic.get_mask()
- segments_slic.reshape((height*width,))
- segments_slic = np.frombuffer(segments_slic, dtype=np.intc, count=height*width).reshape((height, width)).copy()
- # Destruct the C++ class
- run_slic.__destruct__()
- return segments_slic
- def slic_mean_intensity_thres(img, segments_slic, thres=60):
- """Thresholding based on SLIC segments' mean intensity.
- Parameters
- -----------
- img : 2D ndarray
- Input image, must be grayscale.
- segments_slic : 2D ndarray
- Integer mask indicating SLIC segment labels.
- thres : int, optional
- Threshold value of mean intensity.
- Returns
- -------
- mask_img : 2D ndarray
- Binary integer mask indicating segment labels.
- """
- mask_img = np.zeros_like(img, dtype='uint8')
- for region in regionprops(segments_slic, img):
- if region.mean_intensity >= thres:
- mask_img.flat[np.ravel_multi_index(region.coords.transpose(), mask_img.shape)] = 255
- return mask_img
- def get_connected_comp(mask_img, structure_size=9):
- """Get connected components inside an image.
- Parameters
- -----------
- mask_img : 2D ndarray
- Input image, must be binary.
- structure_size : int, optional
- Structure size for binary dilation before connected component labeling.
- Returns
- -------
- output : 2D ndarray
- Integer mask indicating connected component labels.
- """
- return label(binary_dilation(mask_img, selem=np.ones((structure_size, structure_size), dtype='int')), connectivity=2)
- def find_tissue(img, connected_comp, prop=0.2):
- """Find the tissue component within a connected component map.
- Parameters
- -----------
- img : 2D ndarray
- Input image, must be grayscale.
- connected_comp : 2D ndarray
- Integer mask indicating connected component labels.
- prop : float, optional
- Proportional occupied area of the entire image to be considered as tissue.
- Returns
- -------
- tissue_img : 2D ndarray
- Binary integer mask indicating the tissue.
- """
- height, width = img.shape
- area_thres = prop * height * width
- tissue_img = np.zeros_like(img, dtype='uint8')
- for region in regionprops(connected_comp, img):
- if region.area >= area_thres:
- tissue_img.flat[np.ravel_multi_index(region.coords.transpose(), tissue_img.shape)] = 255
- return tissue_img
- def find_refine_boundaries(img, segments_slic, tissue_img):
- """Find and refine boundaries of the tissue image using convex hull.
- Parameters
- -----------
- img : 2D ndarray
- Input image, must be grayscale.
- segments_slic : 2D ndarray
- Integer mask indicating SLIC segment labels.
- tissue_img : 2D ndarray
- Binary integer mask indicating the tissue.
- Returns
- -------
- tissue_img : 2D ndarray
- Binary integer refined mask indicating the tissue.
- """
- # Find boundaries
- boundaries = find_boundaries(tissue_img, connectivity=2, mode='thick')
- labels = np.unique(segments_slic[boundaries])
- # Convex hull on boundaries
- for region in regionprops(segments_slic, img):
- if region.label in labels:
- min_r, min_c, max_r, max_c = region.bbox
- img_tile = tissue_img[min_r:max_r, min_c:max_c]
- outlier_hull_img = convex_hull_image(img_tile)
- tissue_img[min_r:max_r, min_c:max_c] = (outlier_hull_img * 255).astype('uint8')
- return tissue_img
- def save_grayscale_img(img, filename):
- """Save a grayscale image locally as filename.
- Parameters
- -----------
- img : 2D ndarray
- Input image, must be grayscale.
- filename : string
- File path and file name to save.
- """
- height, width = img.shape
- # Convert numpy array into PIL image and save to local
- # Divide the WSI into regular slices to avoid PIL buffer overflow
- slice_size = 30000
- iters = np.uint8(np.ceil([height / slice_size, width / slice_size]))
- save_img = Image.new('L', (width, height))
- for row in range(iters[0]):
- for col in range(iters[1]):
- # Get start and end pixel location
- start_r, start_c = row * slice_size, col * slice_size
- end_r, end_c = min(height, start_r + slice_size), min(width, start_c + slice_size)
- # Paste the slice mask to image mask
- save_slice = Image.fromarray(img[start_r:end_r, start_c:end_c], 'L')
- save_img.paste(save_slice, (start_c, start_r))
- # Save masked image
- save_img.save(filename)
- # %%
- # Final Pipeline
- currentDT = datetime.datetime.now(); print(str(currentDT)); totalT = 0.0; startT = time()
- img = np.array(Image.open('data/outputs/norm_png/NA3777-02_AB_Eosin.png'))
- # Binary Thresholding to improve contrast
- img = np.where(img < 15, 0, 255).astype('uint8')
- print("Loaded Image: %s" % (time()-startT)); totalT += (time()-startT); startT = time()
- segments_slic = run_slic_seg(img)
- print("Total SLIC: %s" % (time()-startT)); totalT += (time()-startT); startT = time()
- # Mean Intensity Thresholding
- mask_img = slic_mean_intensity_thres(img, segments_slic)
- print("Mean Intensity Thresholding: %s" % (time()-startT)); totalT += (time()-startT); startT = time()
- # Connected components
- connected_comp = get_connected_comp(mask_img)
- del mask_img
- print("Connected component labeling: %s" % (time()-startT)); totalT += (time()-startT); startT = time()
- # Find tissue component
- tissue_img = find_tissue(img, connected_comp)
- del connected_comp
- print("Find tissue component: %s" % (time()-startT)); totalT += (time()-startT); startT = time()
- # Find and refine boundaries
- tissue_img = find_refine_boundaries(img, segments_slic, tissue_img)
- del img, segments_slic
- print("Find and refine boundaries: %s" % (time()-startT)); totalT += (time()-startT); startT = time()
- # Save image
- save_grayscale_img(tissue_img, 'NA3777-02_AB_Eosin_SLICO_0.01_iter=5_80000_finalmask.png')
- print("Saved: %s" % (time()-startT)); totalT += (time()-startT)
- print("Total: %s" % totalT)
- # %% [markdown]
- # ### Timing Results
- # | SLIC #Segments | Load Image | SLIC | Mean Intensity Thresholding | Connected Component | Find Tissue | Find Boundaries | Convex Hull | Save Output | Total |
- # |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
- # | 40000 | 27.6471 | 232.6231 | 57.3725 | 106.8111 | 58.1894 | 137.0627 | 33.8033 | 48.8813 | 702.3917 |
- # | 60000 | 30.0168 | 206.1811 | 60.3673 | 110.6402 | 59.3389 | 140.0935 | 31.8562 | 49.2528 | 687.7481 |
- # | 80000 | 27.9608 | 249.4539 | 58.0493 | 107.5019 | 57.5752 | 136.3321 | 30.8568 | 49.1053 | 716.8363 |
- # %% [markdown]
- # ## Pipeline Development
- # ### Latest Scikit-image Canny Algorithm
- # %%
- """
- canny.py - Canny Edge detector
- Reference: Canny, J., A Computational Approach To Edge Detection, IEEE Trans.
- Pattern Analysis and Machine Intelligence, 8:679-714, 1986
- Originally part of CellProfiler, code licensed under both GPL and BSD licenses.
- Website: http://www.cellprofiler.org
- Copyright (c) 2003-2009 Massachusetts Institute of Technology
- Copyright (c) 2009-2011 Broad Institute
- All rights reserved.
- Original author: Lee Kamentsky
- """
- import numpy as np
- import scipy.ndimage as ndi
- from scipy.ndimage import generate_binary_structure
- from skimage.filters import gaussian
- from skimage import dtype_limits, img_as_float
- from skimage._shared.utils import assert_nD
- def smooth_with_function_and_mask(image, function, mask):
- """Smooth an image with a linear function, ignoring masked pixels
- Parameters
- ----------
- image : array
- Image you want to smooth.
- function : callable
- A function that does image smoothing.
- mask : array
- Mask with 1's for significant pixels, 0's for masked pixels.
- Notes
- ------
- This function calculates the fractional contribution of masked pixels
- by applying the function to the mask (which gets you the fraction of
- the pixel data that's due to significant points). We then mask the image
- and apply the function. The resulting values will be lower by the
- bleed-over fraction, so you can recalibrate by dividing by the function
- on the mask to recover the effect of smoothing from just the significant
- pixels.
- """
- bleed_over = function(mask.astype(float))
- masked_image = np.zeros(image.shape, image.dtype)
- masked_image[mask] = image[mask]
- smoothed_image = function(masked_image)
- output_image = smoothed_image / (bleed_over + np.finfo(float).eps)
- return output_image
- def canny(image, sigma=1., low_threshold=None, high_threshold=None, mask=None,
- use_quantiles=False):
- """Edge filter an image using the Canny algorithm.
- Parameters
- -----------
- image : 2D array
- Grayscale input image to detect edges on; can be of any dtype.
- sigma : float
- Standard deviation of the Gaussian filter.
- low_threshold : float
- Lower bound for hysteresis thresholding (linking edges).
- If None, low_threshold is set to 10% of dtype's max.
- high_threshold : float
- Upper bound for hysteresis thresholding (linking edges).
- If None, high_threshold is set to 20% of dtype's max.
- mask : array, dtype=bool, optional
- Mask to limit the application of Canny to a certain area.
- use_quantiles : bool, optional
- If True then treat low_threshold and high_threshold as quantiles of the
- edge magnitude image, rather than absolute edge magnitude values. If True
- then the thresholds must be in the range [0, 1].
- Returns
- -------
- output : 2D array (image)
- The binary edge map.
- See also
- --------
- skimage.sobel
- Notes
- -----
- The steps of the algorithm are as follows:
- * Smooth the image using a Gaussian with ``sigma`` width.
- * Apply the horizontal and vertical Sobel operators to get the gradients
- within the image. The edge strength is the norm of the gradient.
- * Thin potential edges to 1-pixel wide curves. First, find the normal
- to the edge at each point. This is done by looking at the
- signs and the relative magnitude of the X-Sobel and Y-Sobel
- to sort the points into 4 categories: horizontal, vertical,
- diagonal and antidiagonal. Then look in the normal and reverse
- directions to see if the values in either of those directions are
- greater than the point in question. Use interpolation to get a mix of
- points instead of picking the one that's the closest to the normal.
- * Perform a hysteresis thresholding: first label all points above the
- high threshold as edges. Then recursively label any point above the
- low threshold that is 8-connected to a labeled point as an edge.
- References
- -----------
- .. [1] Canny, J., A Computational Approach To Edge Detection, IEEE Trans.
- Pattern Analysis and Machine Intelligence, 8:679-714, 1986
- .. [2] William Green's Canny tutorial
- http://dasl.unlv.edu/daslDrexel/alumni/bGreen/www.pages.drexel.edu/_weg22/can_tut.html
- Examples
- --------
- >>> from skimage import feature
- >>> # Generate noisy image of a square
- >>> im = np.zeros((256, 256))
- >>> im[64:-64, 64:-64] = 1
- >>> im += 0.2 * np.random.rand(*im.shape)
- >>> # First trial with the Canny filter, with the default smoothing
- >>> edges1 = feature.canny(im)
- >>> # Increase the smoothing for better results
- >>> edges2 = feature.canny(im, sigma=3)
- """
- #
- # The steps involved:
- #
- # * Smooth using the Gaussian with sigma above.
- #
- # * Apply the horizontal and vertical Sobel operators to get the gradients
- # within the image. The edge strength is the sum of the magnitudes
- # of the gradients in each direction.
- #
- # * Find the normal to the edge at each point using the arctangent of the
- # ratio of the Y sobel over the X sobel - pragmatically, we can
- # look at the signs of X and Y and the relative magnitude of X vs Y
- # to sort the points into 4 categories: horizontal, vertical,
- # diagonal and antidiagonal.
- #
- # * Look in the normal and reverse directions to see if the values
- # in either of those directions are greater than the point in question.
- # Use interpolation to get a mix of points instead of picking the one
- # that's the closest to the normal.
- #
- # * Label all points above the high threshold as edges.
- # * Recursively label any point above the low threshold that is 8-connected
- # to a labeled point as an edge.
- #
- # Regarding masks, any point touching a masked point will have a gradient
- # that is "infected" by the masked point, so it's enough to erode the
- # mask by one and then mask the output. We also mask out the border points
- # because who knows what lies beyond the edge of the image?
- #
- assert_nD(image, 2)
- dtype_max = dtype_limits(image, clip_negative=False)[1]
- if low_threshold is None:
- low_threshold = 0.1
- else:
- low_threshold = low_threshold / dtype_max
- if high_threshold is None:
- high_threshold = 0.2
- else:
- high_threshold = high_threshold / dtype_max
- if mask is None:
- mask = np.ones(image.shape, dtype=bool)
- def fsmooth(x):
- return img_as_float(gaussian(x, sigma, mode='constant'))
- smoothed = smooth_with_function_and_mask(image, fsmooth, mask)
- jsobel = ndi.sobel(smoothed, axis=1)
- isobel = ndi.sobel(smoothed, axis=0)
- abs_isobel = np.abs(isobel)
- abs_jsobel = np.abs(jsobel)
- magnitude = np.hypot(isobel, jsobel)
- #
- # Make the eroded mask. Setting the border value to zero will wipe
- # out the image edges for us.
- #
- s = generate_binary_structure(2, 2)
- eroded_mask = ndi.binary_erosion(mask, s, border_value=0)
- eroded_mask = eroded_mask & (magnitude > 0)
- #
- #--------- Find local maxima --------------
- #
- # Assign each point to have a normal of 0-45 degrees, 45-90 degrees,
- # 90-135 degrees and 135-180 degrees.
- #
- local_maxima = np.zeros(image.shape, bool)
- #----- 0 to 45 degrees ------
- pts_plus = (isobel >= 0) & (jsobel >= 0) & (abs_isobel >= abs_jsobel)
- pts_minus = (isobel <= 0) & (jsobel <= 0) & (abs_isobel >= abs_jsobel)
- pts = pts_plus | pts_minus
- pts = eroded_mask & pts
- # Get the magnitudes shifted left to make a matrix of the points to the
- # right of pts. Similarly, shift left and down to get the points to the
- # top right of pts.
- c1 = magnitude[1:, :][pts[:-1, :]]
- c2 = magnitude[1:, 1:][pts[:-1, :-1]]
- m = magnitude[pts]
- w = abs_jsobel[pts] / abs_isobel[pts]
- c_plus = c2 * w + c1 * (1 - w) <= m
- c1 = magnitude[:-1, :][pts[1:, :]]
- c2 = magnitude[:-1, :-1][pts[1:, 1:]]
- c_minus = c2 * w + c1 * (1 - w) <= m
- local_maxima[pts] = c_plus & c_minus
- #----- 45 to 90 degrees ------
- # Mix diagonal and vertical
- #
- pts_plus = (isobel >= 0) & (jsobel >= 0) & (abs_isobel <= abs_jsobel)
- pts_minus = (isobel <= 0) & (jsobel <= 0) & (abs_isobel <= abs_jsobel)
- pts = pts_plus | pts_minus
- pts = eroded_mask & pts
- c1 = magnitude[:, 1:][pts[:, :-1]]
- c2 = magnitude[1:, 1:][pts[:-1, :-1]]
- m = magnitude[pts]
- w = abs_isobel[pts] / abs_jsobel[pts]
- c_plus = c2 * w + c1 * (1 - w) <= m
- c1 = magnitude[:, :-1][pts[:, 1:]]
- c2 = magnitude[:-1, :-1][pts[1:, 1:]]
- c_minus = c2 * w + c1 * (1 - w) <= m
- local_maxima[pts] = c_plus & c_minus
- #----- 90 to 135 degrees ------
- # Mix anti-diagonal and vertical
- #
- pts_plus = (isobel <= 0) & (jsobel >= 0) & (abs_isobel <= abs_jsobel)
- pts_minus = (isobel >= 0) & (jsobel <= 0) & (abs_isobel <= abs_jsobel)
- pts = pts_plus | pts_minus
- pts = eroded_mask & pts
- c1a = magnitude[:, 1:][pts[:, :-1]]
- c2a = magnitude[:-1, 1:][pts[1:, :-1]]
- m = magnitude[pts]
- w = abs_isobel[pts] / abs_jsobel[pts]
- c_plus = c2a * w + c1a * (1.0 - w) <= m
- c1 = magnitude[:, :-1][pts[:, 1:]]
- c2 = magnitude[1:, :-1][pts[:-1, 1:]]
- c_minus = c2 * w + c1 * (1.0 - w) <= m
- local_maxima[pts] = c_plus & c_minus
- #----- 135 to 180 degrees ------
- # Mix anti-diagonal and anti-horizontal
- #
- pts_plus = (isobel <= 0) & (jsobel >= 0) & (abs_isobel >= abs_jsobel)
- pts_minus = (isobel >= 0) & (jsobel <= 0) & (abs_isobel >= abs_jsobel)
- pts = pts_plus | pts_minus
- pts = eroded_mask & pts
- c1 = magnitude[:-1, :][pts[1:, :]]
- c2 = magnitude[:-1, 1:][pts[1:, :-1]]
- m = magnitude[pts]
- w = abs_jsobel[pts] / abs_isobel[pts]
- c_plus = c2 * w + c1 * (1 - w) <= m
- c1 = magnitude[1:, :][pts[:-1, :]]
- c2 = magnitude[1:, :-1][pts[:-1, 1:]]
- c_minus = c2 * w + c1 * (1 - w) <= m
- local_maxima[pts] = c_plus & c_minus
- #
- #---- If use_quantiles is set then calculate the thresholds to use
- #
- if use_quantiles:
- if high_threshold > 1.0 or low_threshold > 1.0:
- raise ValueError("Quantile thresholds must not be > 1.0")
- if high_threshold < 0.0 or low_threshold < 0.0:
- raise ValueError("Quantile thresholds must not be < 0.0")
- high_threshold = np.percentile(magnitude, 100.0 * high_threshold)
- low_threshold = np.percentile(magnitude, 100.0 * low_threshold)
- #
- #---- Create two masks at the two thresholds.
- #
- high_mask = local_maxima & (magnitude >= high_threshold)
- low_mask = local_maxima & (magnitude >= low_threshold)
- #
- # Segment the low-mask, then only keep low-segments that have
- # some high_mask component in them
- #
- strel = np.ones((3, 3), bool)
- labels, count = ndi.label(low_mask, strel)
- if count == 0:
- return low_mask
- sums = (np.array(ndi.sum(high_mask, labels,
- np.arange(count, dtype=np.int32) + 1),
- copy=False, ndmin=1))
- good_label = np.zeros((count + 1,), bool)
- good_label[1:] = sums > 0
- output_mask = good_label[labels]
- return output_mask
- # %% [markdown]
- # ### Superpixel
- # %%
- # Superpixel
- from skimage.segmentation import slic, mark_boundaries
- from skimage.util import img_as_float
- img = np.array(Image.open('data/outputs/norm_png/NA3777-02_AB_Eosin.png'))
- # Binary Thresholding
- img = np.where(img < 15, 0, 255).astype('uint8')
- height, width = img.shape
- print(img.shape)
- # %%
- import cppyy
- cppyy.load_library('gSLICr/build/libgSLICr_lib.so')
- cppyy.load_library('/usr/local/cuda-10.0/targets/x86_64-linux/lib/libcudart.so')
- cppyy.add_include_path('/usr/local/cuda-10.0/targets/x86_64-linux/include') # Add cuda library
- cppyy.include('gSLICr/gSLICr_Lib/gSLICr.h')
- from cppyy.gbl import gSLICr
- import cppyy.ll
- from skimage.segmentation import mark_boundaries
- currentDT = datetime.datetime.now(); print(str(currentDT)); startT = time()
- # Create a Run_SLIC C++ class
- run_slic = gSLICr.Run_SLIC()
- print("Run_SLIC: %s" % (time()-startT))
- # Specify settings
- gslic_settings = run_slic.get_settings()
- gslic_settings = cppyy.bind_object(gslic_settings, gSLICr.objects.settings)
- gslic_settings.img_size.x = width
- gslic_settings.img_size.y = height
- gslic_settings.no_segs = 60000
- gslic_settings.no_iters = 5
- gslic_settings.coh_weight = 0.01
- gslic_settings.do_enforce_connectivity = True
- gslic_settings.slic_zero = True
- gslic_settings.color_space = gSLICr.GRAY
- gslic_settings.seg_method = gSLICr.GIVEN_NUM
- run_slic.print_settings()
- # Start running gSLIC
- run_slic.run(img)
- print("run: %s" % (time()-startT))
- # Get the segmentation mask
- segments_slic = run_slic.get_mask()
- print("get_mask: %s" % (time()-startT))
- segments_slic.reshape((height*width,))
- segments_slic = np.frombuffer(segments_slic, dtype=np.intc, count=height*width).reshape((height, width)).copy()
- print("reshape: %s" % (time()-startT))
- run_slic.__destruct__()
- print("__destruct__: %s" % (time()-startT))
- # 81 to 73 after block-dim=32 using slic iter=0
- # Down to 50 after changing from Float3Image to FloatImage
- np.save('segments_slico_0.01_iter=5_60000', segments_slic)
- print("save as npy: %s" % (time()-startT))
- # Scale [0,1] to [0,255]
- def uint8scale(arr, _min=0, _max=1):
- return ((arr - _min) * 1/(_max - _min) * 255).astype('uint8')
- print('SLIC number of segments: {}'.format(len(np.unique(segments_slic))))
- mark_img = mark_boundaries(img, segments_slic, outline_color=(1,1,0), mode='thick')
- print('Marked')
- del segments_slic#, img
- norm_img = uint8scale(mark_img)
- print('Normalized')
- del mark_img
- save_img = Image.fromarray(norm_img, 'RGB')
- save_img.save('NA3777-02_AB_Eosin_SLICO_0.01_iter=5_60000.png')
- del save_img
- print('Saved')
- # %%
- # Superpixel
- from skimage.segmentation import slic, mark_boundaries
- from skimage.util import img_as_float
- # Scale [0,1] to [0,255]
- def uint8scale(arr, _min=0, _max=1):
- return ((arr - _min) * 1/(_max - _min) * 255).astype('uint8')
- img = np.array(Image.open('data/outputs/norm_png/NA3777-02_AB_Eosin.png'))
- # Binary Thresholding
- img = np.where(img < 15, 0, 255).astype('uint8')
- height, width = img.shape
- print(img.shape)
- img = img_as_float(img)
- # Max memory 123+16 using float64. 255 segments. Run time: 1324 seconds = 22 minutes
- currentDT = datetime.datetime.now(); print(str(currentDT)); startT = time()
- segments_slic = slic(img, n_segments=5000, compactness=0.01, slic_zero=True)
- print("SLIC: %s" % (time()-startT))
- print('SLIC number of segments: {}'.format(len(np.unique(segments_slic))))
- del img
- img = np.array(Image.open('data/outputs/norm_png/NA3777-02_AB_Eosin.png'))
- # Binary Thresholding
- img = np.where(img < 15, 0, 255).astype('uint8')
- print('Loaded')
- mark_img = mark_boundaries(img, segments_slic, outline_color=(1,1,0), mode='thick')
- print('Marked')
- del segments_slic, img
- norm_img = uint8scale(mark_img)
- print('Normalized')
- del mark_img
- save_img = Image.fromarray(norm_img, 'RGB')
- save_img.save('NA3777-02_AB_Eosin_SLIC.png')
- print('Saved')
- print("Total: %s" % (time()-startT))
- # %% [markdown]
- # ### Mean Intensity Threshold
- # %%
- segments_slic = np.load('segments_slico_0.01_iter=5_60000.npy')
- img = np.array(Image.open('data/outputs/norm_png/NA3777-02_AB_Eosin.png'))
- # Binary Thresholding
- img = np.where(img < 15, 0, 255).astype('uint8')
- height, width = img.shape
- # %%
- from skimage.measure import regionprops
- mean_intensity_thres = 60
- mask_img = np.zeros_like(img, dtype='uint8')
- for region in regionprops(segments_slic, img):
- if region.mean_intensity >= mean_intensity_thres:
- mask_img.flat[np.ravel_multi_index(region.coords.transpose(), mask_img.shape)] = 255
- # %%
- # Save superpixel with average intensity
- from skimage.measure import regionprops
- segments_slic = np.load('segments_slico_0.1_iter=5_40000.npy')
- img = np.array(Image.open('data/outputs/norm_png/NA3777-02_AB_Eosin.png'))
- # Binary Thresholding
- img = np.where(img < 15, 0, 255).astype('uint8')
- height, width = img.shape
- mask_img = np.zeros_like(img, dtype='uint8')
- for region in regionprops(segments_slic, img):
- mask_img.flat[np.ravel_multi_index(region.coords.transpose(), mask_img.shape)] = region.mean_intensity
- # Convert numpy array into PIL image and save to local
- # Divide the WSI into regular slices to avoid PIL buffer overflow
- slice_size = 30000
- iters = np.uint8(np.ceil([height / slice_size, width / slice_size]))
- save_img = Image.new('L', (width, height))
- for row in range(iters[0]):
- for col in range(iters[1]):
- # Get start and end pixel location
- start_r, start_c = row * slice_size, col * slice_size
- end_r, end_c = min(height, start_r + slice_size), min(width, start_c + slice_size)
- # Paste the slice mask to image mask
- save_slice = Image.fromarray(mask_img[start_r:end_r, start_c:end_c], 'L')
- save_img.paste(save_slice, (start_c, start_r))
- # Save masked image
- save_img.save('NA3777-02_AB_Eosin_SLIC_0.1_iter=5_40000_meanintensity.png')
- # %% [markdown]
- # ### Connected Component
- # %%
- from skimage.measure import label
- from skimage.morphology import binary_dilation
- currentDT = datetime.datetime.now(); print(str(currentDT)); startT = time()
- connected_comp = label(binary_dilation(mask_img, selem=np.ones((9, 9), dtype='int')), connectivity=2)
- print("Connected component labeling: %s" % (time()-startT))
- print('Number of connected components: {}'.format(len(np.unique(connected_comp))))
- # %%
- for region in regionprops(connected_comp, img):
- print(region.area)
- # %%
- mask_img2 = np.zeros_like(img, dtype='uint8')
- for region in regionprops(connected_comp, img):
- if region.area >= 1e8:
- mask_img2.flat[np.ravel_multi_index(region.coords.transpose(), mask_img2.shape)] = 255
- # %%
- # Save connected component image
- # Convert numpy array into PIL image and save to local
- # Divide the WSI into regular slices to avoid PIL buffer overflow
- slice_size = 30000
- iters = np.uint8(np.ceil([height / slice_size, width / slice_size]))
- save_img = Image.new('L', (width, height))
- for row in range(iters[0]):
- for col in range(iters[1]):
- # Get start and end pixel location
- start_r, start_c = row * slice_size, col * slice_size
- end_r, end_c = min(height, start_r + slice_size), min(width, start_c + slice_size)
- # Paste the slice mask to image mask
- save_slice = Image.fromarray(mask_img2[start_r:end_r, start_c:end_c], 'L')
- save_img.paste(save_slice, (start_c, start_r))
- # Save masked image
- save_img.save('NA3777-02_AB_Eosin_SLIC_0.01_iter=5_40000_connectedcomp=25.png')
- # %% [markdown]
- # ### Find and Extend Boundaries
- # %%
- from skimage.segmentation import find_boundaries
- boundaries = find_boundaries(mask_img2, connectivity=2, mode='thick')
- # %% [markdown]
- # ### Associate with SLICO labels
- # %%
- labels = np.unique(segments_slic[boundaries])
- print(len(labels))
- # %%
- # Keep only medium intensity labels
- lower_thres = 60
- edge_labels = []
- for region in regionprops(segments_slic, img):
- mean_inten = region.mean_intensity
- if region.label in labels and mean_inten >= lower_thres:
- edge_labels.append(region.label)
- print(len(edge_labels))
- # %%
- # Mark boundaries for visualization
- del mask_img, mask_img2, connected_comp, boundaries
- from skimage.segmentation import mark_boundaries
- mark_img = mark_boundaries(img, segments_slic, mode='thick')
- # Scale to uint8
- mark_img *= 255
- mark_img = mark_img.astype('uint8')
- # %%
- # dev
- import matplotlib.patches as patches
- # from skimage.feature import canny
- from skimage.morphology import convex_hull_image
- from IPython import display
- #from pyod.models.ocsvm import OCSVM
- #from pyod.models.cof import COF
- #from pyod.models.abod import ABOD
- #from pyod.models.lscp import LSCP
- #from pyod.models.loci import LOCI
- from pyod.models.knn import KNN
- from pyod.models.iforest import IForest
- from pyod.models.cblof import CBLOF
- from pyod.models.feature_bagging import FeatureBagging
- from pyod.models.lof import LOF
- from pyod.models.hbos import HBOS
- from skimage.morphology import binary_erosion
- from pyod.models.pca import PCA
- from pyod.models.mcd import MCD
- nrows = 10
- ncols = 6
- fig, axes = plt.subplots(nrows, ncols, figsize=(16,20), dpi=300)
- axes = axes.reshape((-1,))
- canny_times = np.zeros(nrows)
- outlier_times = np.zeros(nrows)
- count = 0
- for region in regionprops(segments_slic, img):
- #if region.label in edge_labels:
- if region.label in labels:
- min_r, min_c, max_r, max_c = region.bbox
- img_tile = img[min_r:max_r, min_c:max_c]
- #axes[count%nrows*ncols].imshow(img_tile, cmap='gray')
- axes[count%nrows*ncols].imshow(mask_img[min_r:max_r, min_c:max_c])
- axes[count%nrows*ncols].set_title('Region {} Label {}'.format(count+1, region.label))
- axes[count%nrows*ncols].set_xticklabels([])
- axes[count%nrows*ncols].set_yticklabels([])
- # Rough location within WSI
- rect = patches.Rectangle((min_c, min_r),max_c-min_c,max_r-min_r,linewidth=10,edgecolor='r',facecolor='r')
- axes[count%nrows*ncols+1].clear()
- axes[count%nrows*ncols+1].add_patch(rect)
- axes[count%nrows*ncols+1].set_xlim((0, img.shape[1]))
- axes[count%nrows*ncols+1].set_ylim((0, img.shape[0]))
- axes[count%nrows*ncols+1].invert_yaxis()
- axes[count%nrows*ncols+1].set_title('Approx Location')
- axes[count%nrows*ncols+1].set_xticklabels([])
- axes[count%nrows*ncols+1].set_yticklabels([])
- # # Canny edge detection
- # canny_times[count%nrows] = time()
- # canny_img = canny(img_tile, sigma=7)
- # canny_times[count%nrows] = time() - canny_times[count%nrows]
- # axes[count%nrows*ncols+2].imshow(canny_img, cmap='gray')
- # axes[count%nrows*ncols+2].set_title('Canny')
- # axes[count%nrows*ncols+2].set_xticklabels([])
- # axes[count%nrows*ncols+2].set_yticklabels([])
- # canny_hull_img = convex_hull_image(canny_img)
- # axes[count%nrows*ncols+3].imshow(canny_hull_img, cmap='gray')
- # axes[count%nrows*ncols+3].set_title('Canny Hull')
- # axes[count%nrows*ncols+3].set_xticklabels([])
- # axes[count%nrows*ncols+3].set_yticklabels([])
- # Outlier removal and convex hull
- canny_times[count%nrows] = time()
- #X = np.column_stack(np.where(img_tile==255)) # (row, col) indices
- X = np.column_stack(np.where(binary_erosion(img_tile, selem=np.ones((5,5))))) # (row, col) indices
- model = HBOS(contamination=0.03, n_bins=100, tol=0.5) # 0.01s Very Good and doesn't eat edges
- model.fit(X)
- outlier_img = np.zeros_like(img_tile, dtype='uint8')
- outlier_img.flat[np.ravel_multi_index(X[np.invert(model.labels_.astype('bool'))].transpose(), outlier_img.shape)] = 255
- outlier_hull_img = convex_hull_image(outlier_img)
- canny_times[count%nrows] = time() - canny_times[count%nrows]
- axes[count%nrows*ncols+2].imshow(outlier_img, cmap='gray')
- axes[count%nrows*ncols+2].set_title('HBOS Outlier Removal')
- axes[count%nrows*ncols+2].set_xticklabels([])
- axes[count%nrows*ncols+2].set_yticklabels([])
- axes[count%nrows*ncols+3].imshow(outlier_hull_img, cmap='gray')
- axes[count%nrows*ncols+3].set_title('Outlier Hull')
- axes[count%nrows*ncols+3].set_xticklabels([])
- axes[count%nrows*ncols+3].set_yticklabels([])
- # Outlier removal and convex hull
- outlier_times[count%nrows] = time()
- #X = np.column_stack(np.where(img_tile==255)) # (row, col) indices
- #X = np.column_stack(np.where(binary_erosion(img_tile, selem=np.ones((3,3))))) # (row, col) indices
- #model = OCSVM(contamination=0.03) # > 10 min
- #model = COF(contamination=0.03) # insufficient memory
- #model = ABOD(contamination=0.03, n_neighbors = 5) # nonpython TypingError
- #detector_list = [LOF(n_neighbors=5), LOF(n_neighbors=10), LOF(n_neighbors=15),
- # LOF(n_neighbors=20), LOF(n_neighbors=25), LOF(n_neighbors=30),
- # LOF(n_neighbors=35), LOF(n_neighbors=40), LOF(n_neighbors=45),
- # LOF(n_neighbors=50)]
- #model = LSCP(detector_list, contamination=0.03) # Combined average LOFs
- #model = LOCI(contamination=0.03) # insufficient memory
- #model = MCD(contamination=0.03) # 34s Good but eats edges 38s 31s
- #model = PCA(contamination=0.03) # 0.02s Good but eats edges 0.04s 0.02s 0.04s
- #model = KNN(contamination=0.5, n_neighbors = 9, method = 'mean', n_jobs=-1) # 1s
- #model = IForest(contamination=0.03, n_jobs=-1, behaviour='new') # 3s Good
- #model = CBLOF(n_clusters=9, contamination=0.03,check_estimator=False, n_jobs=-1) # 1.5-5s OK
- #model = FeatureBagging(HBOS(contamination=0.03, n_bins=10, tol=0.5), contamination=0.03, n_jobs=-1) # 9-15s Very Good
- #model = HBOS(contamination=0.03, n_bins=100, tol=0.5) # 0.01s Very Good and doesn't eat edges 0.1s 0.01s 0.02s
- #model = LOF(n_neighbors=35, contamination=0.03, n_jobs=-1) # 1s Very Good
- #model.fit(X)
- #outlier_img = np.zeros_like(img_tile, dtype='uint8')
- #outlier_img.flat[np.ravel_multi_index(X[np.invert(model.labels_.astype('bool'))].transpose(), outlier_img.shape)] = 255
- outlier_hull_img = convex_hull_image(img_tile)
- outlier_times[count%nrows] = time() - outlier_times[count%nrows]
- axes[count%nrows*ncols+4].imshow(outlier_img, cmap='gray')
- axes[count%nrows*ncols+4].set_title('IForest Outlier Removal')
- axes[count%nrows*ncols+4].set_xticklabels([])
- axes[count%nrows*ncols+4].set_yticklabels([])
- axes[count%nrows*ncols+5].imshow(outlier_hull_img, cmap='gray')
- axes[count%nrows*ncols+5].set_title('Outlier Hull')
- axes[count%nrows*ncols+5].set_xticklabels([])
- axes[count%nrows*ncols+5].set_yticklabels([])
- if count%nrows == (nrows-1) or count == len(edge_labels)-1:
- print('Average HBOS Runtime: {} sec'.format(np.mean(canny_times)))
- print('Average IForest Runtime: {} sec'.format(np.mean(outlier_times)))
- display.display(fig)
- input("Press Enter to continue...")
- display.clear_output(wait=True)
- count += 1
- print('Done')
- # %%
- # dev-final
- import matplotlib.patches as patches
- from skimage.morphology import convex_hull_image
- from pyod.models.hbos import HBOS
- from skimage.morphology import binary_erosion
- currentDT = datetime.datetime.now(); print(str(currentDT)); startT = time()
- mask_img3 = np.copy(mask_img2)
- for region in regionprops(segments_slic, img):
- if region.label in labels:
- min_r, min_c, max_r, max_c = region.bbox
- img_tile = img[min_r:max_r, min_c:max_c]
- # Outlier removal and convex hull
- X = np.column_stack(np.where(binary_erosion(img_tile, selem=np.ones((5,5))))) # (row, col) indices
- model = HBOS(contamination=0.03, n_bins=100, tol=0.5) # 0.01s Very Good and doesn't eat edges
- model.fit(X)
- outlier_img = np.zeros_like(img_tile, dtype='uint8')
- outlier_img.flat[np.ravel_multi_index(X[np.invert(model.labels_.astype('bool'))].transpose(), outlier_img.shape)] = 255
- outlier_hull_img = convex_hull_image(outlier_img)
- mask_img3[min_r:max_r, min_c:max_c] = (outlier_hull_img * 255).astype('uint8')
- print("Outlier removal and convex hull: %s" % (time()-startT))
- # Convert numpy array into PIL image and save to local
- # Divide the WSI into regular slices to avoid PIL buffer overflow
- slice_size = 30000
- iters = np.uint8(np.ceil([height / slice_size, width / slice_size]))
- save_img = Image.new('L', (width, height))
- for row in range(iters[0]):
- for col in range(iters[1]):
- # Get start and end pixel location
- start_r, start_c = row * slice_size, col * slice_size
- end_r, end_c = min(height, start_r + slice_size), min(width, start_c + slice_size)
- # Paste the slice mask to image mask
- save_slice = Image.fromarray(mask_img3[start_r:end_r, start_c:end_c], 'L')
- save_img.paste(save_slice, (start_c, start_r))
- # Save masked image
- save_img.save('NA3777-02_AB_Eosin_SLICO_0.01_iter=5_40000_finalmask.png')
- print('Saved')
- print("Total: %s" % (time()-startT))
- # %%
- # Convex hull on boundaries
- from skimage.morphology import convex_hull_image
- currentDT = datetime.datetime.now(); print(str(currentDT)); startT = time()
- mask_img3 = np.copy(mask_img2)
- for region in regionprops(segments_slic, img):
- if region.label in labels:
- min_r, min_c, max_r, max_c = region.bbox
- img_tile = mask_img2[min_r:max_r, min_c:max_c]
- # Outlier removal and convex hull
- outlier_hull_img = convex_hull_image(img_tile)
- mask_img3[min_r:max_r, min_c:max_c] = (outlier_hull_img * 255).astype('uint8')
- print("Outlier removal and convex hull: %s" % (time()-startT))
- # Convert numpy array into PIL image and save to local
- # Divide the WSI into regular slices to avoid PIL buffer overflow
- slice_size = 30000
- iters = np.uint8(np.ceil([height / slice_size, width / slice_size]))
- save_img = Image.new('L', (width, height))
- for row in range(iters[0]):
- for col in range(iters[1]):
- # Get start and end pixel location
- start_r, start_c = row * slice_size, col * slice_size
- end_r, end_c = min(height, start_r + slice_size), min(width, start_c + slice_size)
- # Paste the slice mask to image mask
- save_slice = Image.fromarray(mask_img3[start_r:end_r, start_c:end_c], 'L')
- save_img.paste(save_slice, (start_c, start_r))
- # Save masked image
- save_img.save('NA3777-02_AB_Eosin_SLICO_0.01_iter=5_60000_finalmask.png')
- print('Saved')
- print("Total: %s" % (time()-startT))
- # %%
- from skimage.measure import regionprops
- from scipy.ndimage.morphology import binary_fill_holes
- count = 0
- for region in regionprops(segments_slic, img):
- if count < 2000:
- count+=1
- continue
- print(region.label)
- print(region.bbox)
- print(region.max_intensity)
- print(region.mean_intensity)
- print(region.min_intensity)
- print(region.slice[0])
- print(region.slice[1])
- print(region.coords)
- fig = plt.figure(figsize=(15,30))
- ax = fig.add_subplot(1, 2, 1)
- ax.imshow(img[region.bbox[0]:region.bbox[2], region.bbox[1]:region.bbox[3]], cmap='gray')
- ax = fig.add_subplot(1, 2, 2)
- ax.imshow(binary_fill_holes(img[region.bbox[0]:region.bbox[2], region.bbox[1]:region.bbox[3]]), cmap='gray', vmin=0, vmax=1)
- break
3) Analysis - Tissue Separation.ipynb at commit d641f57, under GPL-3.0 · at the source
Overview
- Department of Pathology and Laboratory Medicine, University of California, Davis, Sacramento, CA, United States
- Department of Computer Science, University of California, Davis, Davis, CA, United States
- Department of Public Health Sciences, University of California, Davis, Davis, CA, United States
- Department of Neurology, Alzheimer’s Disease Research Center, University of California, Davis, School of Medicine, Sacramento, CA, United States
- Department of Neurology, Emory University School of Medicine, Atlanta, GA, United States
- Department of Psychiatry, Emory University School of Medicine, Atlanta, GA, United States
- Department of Biomedical Informatics, Emory University School of Medicine, Atlanta, GA, United States
- Department of Neurology, University of California, San Diego, La Jolla, CA, United States
- Department of Neurology, Taub Institute for Research on Alzheimer’s Disease and Aging Brain, Columbia University Medical Center, New York, NY, United States
- Department of Pathology and Cell Biology, Columbia University Medical Center, New York, NY, United States
- Department of Electrical and Computer Engineering, University of California, Davis, Davis, CA, United States
Abstract
Machine learning enables scalable quantification of neuropathology, offering deeper phenotyping of Alzheimer’s disease (AD). In this validation study, we quantified amyloid-beta (Aβ) deposits, evaluating multiple brain regions across institutions, and evaluated associations with clinical, demographic, and genetic factors in persons pathologically diagnosed with AD. All linear models were adjusted for sex, age of death, ethnicity, and center. We analyzed densities (#/
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 1 match between paragraphs and lines of code.
ucdrubinet/BrainSec
d641f5702d35be8c1ef234c704875808b9b6c0d1, 9 March 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
81 files
- Plaque_Quantification.ip
ynb , Jupyter, 812 lines - notebook/
1) Preprocessing - Reinhard Normalization and WSI Tiling.ipynb , Jupyter, 114 lines - notebook/
2) Visualization - Prediction Confidence Heatmaps.ipynb , Jupyter, 507 lines - notebook/
3) Analysis - Plaque Density Distribution.ipynb , Jupyter, 1,226 lines - notebook/
3) Analysis - Tissue Separation.ipynb , Jupyter, 1,223 lines, 1 match - notebook/
ComputeMaskAccuracy.ipyn , Jupyter, 526 linesb - notebook/
Convert_binary_mask_to_x , Jupyter, 94 linesml.ipynb - notebook/
Grad-CAM.ipynb , Jupyter, 288 lines - notebook/
Mask_Accuracy_Benchmark_ , Jupyter, 153 linesPlotting.ipynb - notebook/
Numpy_Memmap_Experiment. , Jupyter, 111 linesipynb - notebook/
Old1.2) Preprocessing - Plaque Detection and Image Cropping.ipynb , Jupyter, 502 lines - notebook/
Old1.3) Preprocessing - Dataset Splitting and Size Filtering.ipynb , Jupyter, 92 lines - notebook/
Old2.1) CNN Models - Model Training and Development.ipynb , Jupyter, 408 lines - notebook/
Old2.2) CNN Models - Test Cases (box).ipynb , Jupyter, 329 lines - notebook/
Old2.2) CNN Models - Test Cases.ipynb , Jupyter, 332 lines - notebook/
Old4.1) Saliency Mapping - Feature Occlusion.ipynb , Jupyter, 205 lines - notebook/
Old4.2) Saliency Mapping - Guided Grad-CAM.ipynb , Jupyter, 387 lines - notebook/
Old5.1) Whole Slide Scoring - Tissue Area WSI Segmentation.ipynb , Jupyter, 766 lines - notebook/
Old5.2) Whole Slide Scoring - Prediction Confidence Segmentation.ipynb , Jupyter, 103 lines - notebook/
Old5.3) Whole Slide Scoring - CNN Score vs. CERAD-like Scores.ipynb , Jupyter, 154 lines - notebook/
Post-processing.ipynb , Jupyter, 125 lines - notebook/
PowerAnalysis.ipynb , Jupyter, 490 lines - notebook/
TensorBoard_Plotting.ipy , Jupyter, 420 linesnb - notebook/
baseline_code/ , Jupyter, 237 lineseval.ipynb - notebook/
baseline_code/ , Jupyter, 109 linesfcn.ipynb - notebook/
baseline_code/ , Python, 261 linesfcn.py - notebook/
baseline_code/ , Jupyter, 288 linestest_visual_model.ipynb - notebook/
baseline_code/ , Jupyter, 205 linestraining.ipynb - pyscripts/
1_preprocessing.py , Python, 110 lines - pyscripts/
1_preprocessing_czi.py , Python, 212 lines - pyscripts/
2_inference.py , Python, 339 lines - pyscripts/
2_inference_czi.py , Python, 399 lines - pyscripts/
3_postprocessing.py , Python, 419 lines - pyscripts/
3_postprocessing_nobrain , Python, 435 linesgsegpostprop.py - qupath/
scripts/ , Python, 118 linesCombineAnnotationTiles.p y - setup.sh, Shell, 245 lines
- src/
gSLICr/ , C/C++, 496 linesNVTimer.h - src/
gSLICr/ , C/C++, 54 linesORUtils/ CUDADefines.h - src/
gSLICr/ , C/C++, 73 linesORUtils/ Cholesky.h - src/
gSLICr/ , C++, 4 linesORUtils/ Dummy.cpp - src/
gSLICr/ , C/C++, 69 linesORUtils/ Image.h - src/
gSLICr/ , C/C++, 29 linesORUtils/ LexicalCast.h - src/
gSLICr/ , C/C++, 57 linesORUtils/ MathUtils.h - src/
gSLICr/ , C/C++, 444 linesORUtils/ Matrix.h - src/
gSLICr/ , C/C++, 293 linesORUtils/ MemoryBlock.h - src/
gSLICr/ , C/C++, 201 linesORUtils/ MemoryBlockPersister.h - src/
gSLICr/ , C/C++, 40 linesORUtils/ MetalContext.h - src/
gSLICr/ , C/C++, 35 linesORUtils/ PlatformIndependence.h - src/
gSLICr/ , C/C++, 851 linesORUtils/ Vector.h - src/
gSLICr/ , C++, 119 linesdemo.cpp - src/
gSLICr/ , C++, 57 linesgSLICr_Lib/ engines/ gSLICr_core_engine.cpp - src/
gSLICr/ , C/C++, 38 linesgSLICr_Lib/ engines/ gSLICr_core_engine.h - src/
gSLICr/ , C++, 61 linesgSLICr_Lib/ engines/ gSLICr_seg_engine.cpp - src/
gSLICr/ , C/C++, 63 linesgSLICr_Lib/ engines/ gSLICr_seg_engine.h - src/
gSLICr/ , CUDA, 393 linesgSLICr_Lib/ engines/ gSLICr_seg_engine_GPU.cu - src/
gSLICr/ , C/C++, 34 linesgSLICr_Lib/ engines/ gSLICr_seg_engine_GPU.h - src/
gSLICr/ , C/C++, 290 linesgSLICr_Lib/ engines/ gSLICr_seg_engine_shared .h - src/
gSLICr/ , C/C++, 83 linesgSLICr_Lib/ gSLICr.h - src/
gSLICr/ , C/C++, 100 linesgSLICr_Lib/ gSLICr_defines.h - src/
gSLICr/ , C/C++, 24 linesgSLICr_Lib/ objects/ gSLICr_settings.h - src/
gSLICr/ , C/C++, 20 linesgSLICr_Lib/ objects/ gSLICr_spixel_info.h - src/
image_helper.py , Python, 292 lines - src/
networks/ , Python, 758 linesdataset.py - src/
networks/ , Python, 118 lineslosses.py - src/
networks/ , Python, 138 linesmetrics.py - src/
networks/ , Python, 112 linesmodels/ FCN.py - src/
networks/ , Python, 171 linesmodels/ UNet.py - src/
networks/ , Python, 58 linesmodels/ models.py - src/
postproc.py , Python, 450 lines - src/
predict.py , Python, 187 lines - src/
tissue_seg.py , Python, 179 lines - src/
train.py , Python, 342 lines - src/
utils/ , Python, 73 linescolor_deconv.py - src/
utils/ , Python, 438 linescompute_mask_accuracy.py - src/
utils/ , Python, 112 linesnumpy_pil_helper.py - src/
utils/ , Python, 521 linesseparate_tissue.py - src/
utils/ , Python, 140 linessvs_to_png.py - tests/
test_model_losses.py , Python, 676 lines - tests/
test_train_predict.py , Python, 236 lines - LICENSE, License, 674 lines
- README.md, Text, 116 lines
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 79 scripts, each with its path and the digest of its content;
- 1 match between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
Datasets cited
- doi:10.5061/
dryad.7h44j107j , at Dryad; found in “DATA AVAILABILITY” - doi:10.5061/
dryad.wstqjq30t , at Dryad; found in DataCite
Data availability
The datasets supporting the findings of this study are publicly available on the DRYAD platform. The dataset comprises over 2 TB of WSI of Aβ-immunostained human brain tissue used in this study. Due to file size constraints, the complete WSI dataset is provided in two parts: Part 1 (DOI: 10.5061/
Reproduced under the paper's license (CC BY-NC), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 15 authors, 6 keywords, 12 MeSH terms, 4 funders, 71 references.
Cite
This paper
Garcia, D., Rai Sharma, S. R., Saito, N., Beckett, L., Sevilla, L. N. C., Vasquez, L. R., DeCarli, C. S., Gutman, D., Vizcarra, J., Coughlin, D. G., Teich, A. F., Garcia, L., Mungas, D. M., Chuah, C.-N., & Dugger, B. N. (2026). Clinical and pathologic correlations of machine learning quantification of Aβ deposits across 3 brain regions of decedents with Alzheimer disease. Journal of neuropathology and experimental neurology, 85(6), 537-550. https://
BibTeX
@article{garcia2026clini
author = {Garcia, David and Rai Sharma, Shivam Rajendra and Saito, Naomi and Beckett, Laurel and Sevilla, Louise Nicole C and Vasquez, La Rissa and DeCarli, Charles S and Gutman, David and Vizcarra, Juan and Coughlin, David G and Teich, Andrew F and Garcia, Lorena and Mungas, Dan M and Chuah, Chen-Nee and Dugger, Brittany N},
title = {{Clinical and pathologic correlations of machine learning quantification of Aβ deposits across 3 brain regions of decedents with Alzheimer disease}},
journal = {Journal of neuropathology and experimental neurology},
year = {2026},
month = jun,
volume = {85},
number = {6},
pages = {537--550},
publisher = {Oxford University Press},
issn = {0022-3069},
doi = {10.1093/
url = {https://
pmid = {41806384},
pmcid = {PMC13197125}
}
RIS
TY - JOUR
AU - Garcia, David
AU - Rai Sharma, Shivam Rajendra
AU - Saito, Naomi
AU - Beckett, Laurel
AU - Sevilla, Louise Nicole C
AU - Vasquez, La Rissa
AU - DeCarli, Charles S
AU - Gutman, David
AU - Vizcarra, Juan
AU - Coughlin, David G
AU - Teich, Andrew F
AU - Garcia, Lorena
AU - Mungas, Dan M
AU - Chuah, Chen-Nee
AU - Dugger, Brittany N
TI - Clinical and pathologic correlations of machine learning quantification of Aβ deposits across 3 brain regions of decedents with Alzheimer disease
T2 - Journal of neuropathology and experimental neurology
J2 - J Neuropathol Exp Neurol
PY - 2026
DA - 2026/
VL - 85
IS - 6
SP - 537
EP - 550
SN - 0022-3069
PB - Oxford University Press
DO - 10.1093/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1093/
"type": "article-journal",
"title": "Clinical and pathologic correlations of machine learning quantification of Aβ deposits across 3 brain regions of decedents with Alzheimer disease",
"container-title": "Journal of neuropathology and experimental neurology",
"author": [
{
"family": "Garcia",
"given": "David"
},
{
"family": "Rai Sharma",
"given": "Shivam Rajendra"
},
{
"family": "Saito",
"given": "Naomi"
},
{
"family": "Beckett",
"given": "Laurel"
},
{
"family": "Sevilla",
"given": "Louise Nicole C"
},
{
"family": "Vasquez",
"given": "La Rissa"
},
{
"family": "DeCarli",
"given": "Charles S"
},
{
"family": "Gutman",
"given": "David"
},
{
"family": "Vizcarra",
"given": "Juan"
},
{
"family": "Coughlin",
"given": "David G"
},
{
"family": "Teich",
"given": "Andrew F"
},
{
"family": "Garcia",
"given": "Lorena"
},
{
"family": "Mungas",
"given": "Dan M"
},
{
"family": "Chuah",
"given": "Chen-Nee"
},
{
"family": "Dugger",
"given": "Brittany N"
}
],
"container-title-short":
"volume": "85",
"issue": "6",
"page": "537-550",
"DOI": "10.1093/
"PMID": "41806384",
"PMCID": "PMC13197125",
"ISSN": "0022-3069",
"publisher": "Oxford University Press",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
1
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s41598-026-61605-4 [code]
- Learning precise segmentation of neurofibrillary tangles from rapid manual point annotations.Journal: Scientific reportsIn common: OpenCV, scikit-image, Pillow, 5 other tools, Alzheimer's / dementia, 9 references
- [2] doi:10.1364/boe.605322 [code]
- Generalized plaque digitization framework for multi-dimensional mesoscopic images.Journal: Biomedical optics expressIn common: Keras, TensorFlow, OpenCV, 8 other tools, 1 reference
- [3] doi:10.1038/s42003-026-10957-8 [code]
- Brain defence by the extracellular matrix protein Cochlin.Journal: Communications biologyIn common: Keras, TensorFlow, OpenCV, 8 other tools
- [4] doi:10.1371/journal.pcbi.1014571 [code]
- SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.Journal: PLoS computational biologyIn common: Keras, TensorFlow, OpenCV, 8 other tools
- [5] doi:10.1126/sciadv.aed3650 [code]
- Truthful visualizations for mass spectrometry imaging enable high-spatial-resolution interactive &
lt;i& gt;m/ z& lt;/ i& gt; mapping and exploration. Journal: Science advancesIn common: Keras, TensorFlow, OpenCV, 8 other tools - [6] doi:10.1038/s41467-026-72057-9 [code]
- Sex-specific behavioral feedback modulates sensorimotor processing and drives flexible social behavior.Journal: Nature communicationsIn common: Keras, TensorFlow, OpenCV, 8 other tools
- [7] doi:10.1002/epi.70296 [code]
- Fully automated three-dimensional deep learning-based magnetic resonance imaging segmentation of brain cavities in epilepsy surgery.Journal: EpilepsiaIn common: Keras, TensorFlow, OpenCV, 7 other tools, clinical / translational
- [8] doi:10.1186/s12880-026-02481-2 [code]
- Deep learning-based neuroanatomical profiling reveals population-specific brain changes in multiple sclerosis: a large-scale Middle Eastern study.Journal: BMC medical imagingIn common: Keras, TensorFlow, OpenCV, 7 other tools, clinical / translational
- [9] doi:10.1016/j.xcrm.2026.102766 [code]
- A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.Journal: Cell reports. MedicineIn common: Keras, TensorFlow, OpenCV, 7 other tools
- [10] doi:10.1016/j.patter.2026.101538 [code]
- A multi-modal foundation model for brain disease diagnosis and medical imaging.Journal: Patterns (New York, N.Y.)In common: TensorFlow, OpenCV, scikit-image, 7 other tools, clinical / translational
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 79 scripts, and 1 match between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:f7e7f6c47b6e6463…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
