Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions example/Demo.ipynb

Large diffs are not rendered by default.

13 changes: 6 additions & 7 deletions example/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from useful_wsi import patch_sampling, visualise_cut
from useful_wsi import roi_binary_mask, white_percentage


def check_for_white(img):
"""
Checking for white
Expand All @@ -17,18 +18,16 @@ def check_for_white(img):
"""
return white_percentage(img, 220, 0.8)


def main():
OPTIONS_APPLYING_MASK = {'mask_level': 2, 'function': roi_binary_mask}
OPTIONS_SAMPLING = {'method': "random_patches", 'analyse_level': 0, 'patch_size': (512, 512),
options_applying_mask = {'mask_level': 2, 'mask_function': roi_binary_mask}
options_sampling = {'sampling_method': "random_patches", 'analyse_level': 0, 'patch_size': (512, 512),
'overlapping': 0, 'list_func': [white_percentage], 'mask_tolerance': 0.3,
'allow_overlapping': False, 'n_samples': 100, 'with_replacement': False}


roi_options = dict(options_applying_mask, **options_sampling)
file_name = "TCGA-CN-4739-01A-02-BS2.fc87f5db-d311-4734-a200-7c7d4885b274.svs"

list_roi = patch_sampling(file_name,
o_mask=OPTIONS_APPLYING_MASK,
o_sampling=OPTIONS_SAMPLING)
list_roi = patch_sampling(file_name, **roi_options)

print('We have so many patches {}.'.format(len(list_roi)))

Expand Down
3 changes: 1 addition & 2 deletions useful_wsi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

"""
from .tissue_segmentation import roi_binary_mask, roi_binary_mask2
from .patch_sampling import patch_sampling, OPTIONS_SAMPLING, OPTIONS_APPLYING_MASK
from .patch_sampling import sample_patch_from_wsi, random_wsi_sampling
from .patch_sampling import patch_sampling
from .utils import *
from .visu_utils import visualise_cut
from .version import __version__
152 changes: 35 additions & 117 deletions useful_wsi/patch_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

import itertools
import numpy as np
from skimage import measure

from .tissue_segmentation import roi_binary_mask
from .utils import find_square, get_size, get_whole_image
from .utils import get_x_y, get_x_y_from_0, open_image


def pj_slice(array_np, point_0, point_1=None):
"""
Allows to slice numpy array's given two points.
Expand All @@ -24,80 +24,8 @@ def pj_slice(array_np, point_0, point_1=None):
result = array_np[x_0:x_1, y_0:y_1]
return result

def sample_patch_from_wsi(slide, slide_png, mask=None, mask_resolution=None,
size_square=(512, 512), analyse_level=0,
list_func=[]):
"""
Sample one image from a slide where mask is 1
"""
slide = open_image(slide)
if mask_resolution is None:
mask_resolution = slide.level_count - 1
size_l = get_size(slide, size_square, analyse_level, mask_resolution)
size_l = np.array(size_l)
if mask is None:
mask = np.ones_like(slide_png)[:, :, 0]
x_mask, y_mask = np.where(mask)
indice = np.random.randint(len(x_mask))
point = np.array([x_mask[indice], y_mask[indice]])
sub_img = pj_slice(slide_png, point - size_l // 2, point + size_l // 2)
criterias = []
for function in list_func:
criterias.append(function(sub_img))
if all(criterias):
para = find_square(slide, point, mask_resolution, analyse_level, size_square)
else:
para = None
return para

def remove_sample_from_mask(slide, para, mask, mask_resolution):
"""
Given a square patch and a mask, removes the patch from mask.
So that it can't be choosen again for instance...
"""
if para is not None:
point_0 = (para[1], para[0])
size_l = (para[2], para[3])
analyse_level = para[4]
point_mask_res = get_x_y_from_0(slide, point_0, mask_resolution)
point_mask_res = np.array(point_mask_res)
size_mask_res = get_size(slide, size_l, analyse_level, mask_resolution)
size_mask_res = np.array(size_mask_res)
start_point = np.array([point_mask_res - size_mask_res, (0, 0)]).min(axis=0)
end_point = start_point + 2*size_mask_res
mask[start_point[0]:end_point[0], start_point[1]:end_point[1]] = 0

return mask

def random_wsi_sampling(n_samples, slide, mask=None,
mask_resolution=None, size_square=(512, 512),
analyse_level=0, with_replacement=False,
list_func=[]):
"""
Randomly generate patches from slide.
"""
list_para = []
if mask_resolution is None:
mask_resolution = slide.level_count - 1
slide_png = get_whole_image(slide, mask_resolution, numpy=True)
if mask is None:
mask = np.ones_like(slide_png)[:, :, 0]
mask = mask.astype('bool')
initial_mask = mask.copy()

for _ in range(n_samples):
para = sample_patch_from_wsi(slide, slide_png, mask, mask_resolution, size_square,
analyse_level, list_func)
mask = remove_sample_from_mask(slide, para, mask, mask_resolution)
if with_replacement:
mask = initial_mask
if para is not None:
list_para.append(para)
if mask.sum() == 0:
break
return list_para

def grid_blob(slide, point_start, point_end, patch_size,
def grid_blob(slide, point_start, point_end, patch_size,
analyse_level, margin=0):
"""
Returns grid for blob
Expand All @@ -111,6 +39,7 @@ def grid_blob(slide, point_start, point_end, patch_size,
list_y = range(point_start[1], point_end[1], size_y - 2*margin)
return list(itertools.product(list_x, list_y))


def correct_patch(coord, slide, analyse_level, patch_size):
"""
Correct patch by shifting so that the whole square patch can fit!
Expand All @@ -124,6 +53,7 @@ def correct_patch(coord, slide, analyse_level, patch_size):
coord = np.array([coord, max_dimensions - 1 - patch_size_0]).min(axis=0)
return coord


def mask_percentage(mask, point, radius, tolerance):
"""
Given a binary image and a point and a radius -> sub_img
Expand All @@ -136,7 +66,8 @@ def mask_percentage(mask, point, radius, tolerance):
accepted = score > tolerance
return accepted

def check_patch(slide, slide_png, mask, coord_grid_0,

def check_patch(slide, slide_png, mask, coord_grid_0,
mask_level, patch_size, analyse_level,
list_func, tolerance=0.5,
allow_overlapping=False):
Expand Down Expand Up @@ -171,58 +102,45 @@ def check_patch(slide, slide_png, mask, coord_grid_0,
return parameters


OPTIONS_APPLYING_MASK = {'mask_level': None, 'function': roi_binary_mask}
OPTIONS_SAMPLING = {'method': None, 'analyse_level': 0, 'patch_size': (512, 512),
'overlapping': 0, 'list_func': [], 'mask_tolerance': 0.5,
'allow_overlapping': False, 'n_samples': 10, 'with_replacement': False}

def patch_sampling(slide, seed=None,
o_mask=OPTIONS_APPLYING_MASK,
o_sampling=OPTIONS_SAMPLING):
def patch_sampling(slide, seed=None, mask_level=None,
mask_function=roi_binary_mask, sampling_method=None,
analyse_level=0, patch_size=None, overlapping=0,
list_func=None, mask_tolerance=0.5, allow_overlapping=False,
n_samples=10, with_replacement=False):
"""
Returns a list of patches from slide given a mask generating method
Returns a list of patches from slide given a mask generating method
and a sampling method
"""
np.random.seed(seed)
slide = open_image(slide)


sampling_method = o_sampling['method']

if o_mask['mask_level'] is not None:
mask_level = o_mask['mask_level']
else:
if patch_size is None:
patch_size = (512, 512)
if list_func is None:
list_func = list()
if mask_level is None:
mask_level = slide.level_count - 1


return_list = []

wsi_tissue = get_whole_image(slide, level=mask_level, numpy=True)
wsi_mask = o_mask['function'](wsi_tissue)

if sampling_method == 'grid': # grid is just grid_etienne with marge = 0
blobs = measure.label(wsi_mask)
list_regions = measure.regionprops(blobs)
for blob in list_regions:
min_row, min_col, max_row, max_col = blob.bbox
point_start_l = min_row, min_col
point_end_l = max_row, max_col
point_start_0 = get_x_y(slide, point_start_l, mask_level)
point_end_0 = get_x_y(slide, point_end_l, mask_level)
margin_mask_level = get_size(slide, (o_sampling['overlapping'], 0),
o_sampling['analyse_level'], mask_level)[0]
grid_coord = grid_blob(slide, point_start_0, point_end_0, o_sampling['patch_size'],
o_sampling['analyse_level'], margin=margin_mask_level)
parameter = check_patch(slide, wsi_tissue, wsi_mask, grid_coord, mask_level,
o_sampling['patch_size'], o_sampling['analyse_level'],
o_sampling['list_func'], tolerance=o_sampling['mask_tolerance'],
allow_overlapping=o_sampling['allow_overlapping'])
return_list = return_list + parameter
wsi_mask = mask_function(wsi_tissue)

min_row, min_col, max_row, max_col = 0, 0, *wsi_mask.shape
point_start_l = min_row, min_col
point_end_l = max_row, max_col
point_start_0 = get_x_y(slide, point_start_l, mask_level)
point_end_0 = get_x_y(slide, point_end_l, mask_level)
margin_mask_level = get_size(slide, (overlapping, 0),
analyse_level, mask_level)[0]
grid_coord = grid_blob(slide, point_start_0, point_end_0, patch_size,
analyse_level, margin=margin_mask_level)
parameter = check_patch(slide, wsi_tissue, wsi_mask, grid_coord,
mask_level, patch_size, analyse_level,
list_func, tolerance=mask_tolerance,
allow_overlapping=allow_overlapping)
if sampling_method == 'grid': # grid is just grid_etienne with marge = 0
return_list = parameter
elif sampling_method == "random_patches":
return_list = random_wsi_sampling(o_sampling['n_samples'], slide, wsi_mask, mask_level,
o_sampling['patch_size'], o_sampling['analyse_level'],
with_replacement=o_sampling['with_replacement'],
list_func=o_sampling['list_func'])
return_list = np.array(parameter)[np.random.choice(len(parameter), n_samples, replace=with_replacement), :].tolist()
elif sampling_method == "random_patches_with_border":
raise NameError('sampling method random_patches_with_border is not yet implemented...')
else:
Expand Down