|
a |
|
b/pathaia/patches/visu.py |
|
|
1 |
# coding: utf8 |
|
|
2 |
"""Useful functions for visualizing patches in WSIs.""" |
|
|
3 |
|
|
|
4 |
import numpy |
|
|
5 |
from skimage.morphology import binary_dilation, disk |
|
|
6 |
import openslide |
|
|
7 |
from typing import Sequence, Tuple, Optional |
|
|
8 |
from ..util.types import NDByteImage, Patch, Coord |
|
|
9 |
|
|
|
10 |
|
|
|
11 |
def preview_from_queries( |
|
|
12 |
slide: openslide.OpenSlide, |
|
|
13 |
queries: Sequence[Patch], |
|
|
14 |
min_res: int = 512, |
|
|
15 |
color: Tuple[int, int, int] = (255, 255, 0), |
|
|
16 |
thickness: int = 2, |
|
|
17 |
cell_size: int = 20, |
|
|
18 |
size_0: Optional[Coord] = None, |
|
|
19 |
) -> NDByteImage: |
|
|
20 |
""" |
|
|
21 |
Give thumbnail with patches displayed. |
|
|
22 |
|
|
|
23 |
Args: |
|
|
24 |
slide: openslide object |
|
|
25 |
queries: patch objects to preview from |
|
|
26 |
min_res: minimum size for the smallest side of the thumbnail (usually the width) |
|
|
27 |
color: rgb color for patch boundaries |
|
|
28 |
thickness: thickness of patch boundaries |
|
|
29 |
cell_size: size of a cell representing a patch in the grid |
|
|
30 |
psize: size of a patch at level 0 |
|
|
31 |
|
|
|
32 |
Returns: |
|
|
33 |
Thumbnail image with patches displayed. |
|
|
34 |
|
|
|
35 |
""" |
|
|
36 |
# get thumbnail first |
|
|
37 |
slide_size = Coord(slide.dimensions) |
|
|
38 |
if size_0 is None: |
|
|
39 |
size_0 = Coord(queries[0].size_0) if len(queries) != 0 else Coord(min_res) |
|
|
40 |
thickness = 2 * (thickness // 2) + 1 |
|
|
41 |
res = slide_size / size_0 * (thickness + cell_size) + thickness |
|
|
42 |
thumb_w = max(min_res, res.x) |
|
|
43 |
thumb_h = max(min_res, res.y) |
|
|
44 |
image = slide.get_thumbnail((thumb_w, thumb_h)) |
|
|
45 |
thumb_size = Coord(image.size) |
|
|
46 |
dsr_x = slide_size[0] / thumb_size[0] |
|
|
47 |
dsr_y = slide_size[1] / thumb_size[1] |
|
|
48 |
image = numpy.array(image)[:, :, 0:3] |
|
|
49 |
# get grid |
|
|
50 |
grid = numpy.zeros((thumb_size.y, thumb_size.x), numpy.uint8) |
|
|
51 |
for query in queries: |
|
|
52 |
# position in queries are absolute |
|
|
53 |
x, y = round(query.position[0] / dsr_x), round(query.position[1] / dsr_y) |
|
|
54 |
dx, dy = round(query.size_0[0] / dsr_x), round(query.size_0[1] / dsr_y) |
|
|
55 |
#? startx = numpy.clip(x, 0, thumb_size.x - 1) |
|
|
56 |
startx = min(x, thumb_size.x - 1) |
|
|
57 |
starty = min(y, thumb_size.y - 1) |
|
|
58 |
endx = min(x + dx, thumb_size.x - 1) |
|
|
59 |
endy = min(y + dy, thumb_size.y - 1) |
|
|
60 |
# horizontal segments |
|
|
61 |
grid[starty, startx:endx] = 1 |
|
|
62 |
grid[endy - 1, startx:endx] = 1 |
|
|
63 |
# vertical segments |
|
|
64 |
grid[starty:endy, startx] = 1 |
|
|
65 |
grid[starty:endy, endx - 1] = 1 |
|
|
66 |
d = disk(thickness//2) |
|
|
67 |
grid = binary_dilation(grid, d) |
|
|
68 |
image[grid] = color |
|
|
69 |
return image |