-
Notifications
You must be signed in to change notification settings - Fork 3
/
nms.py
101 lines (90 loc) · 3.92 KB
/
nms.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import numpy as np
from wholeslidedata.annotation.structures import Point
from wholeslidedata.annotation.wholeslideannotation import WholeSlideAnnotation
from wholeslidedata.image.wholeslideimage import WholeSlideImage
from wholeslidedata.labels import Label
def non_max_suppression_fast(boxes, overlapThresh):
"""Very efficient NMS function taken from pyimagesearch"""
# if there are no boxes, return an empty list
if len(boxes) == 0:
return []
# if the bounding boxes integers, convert them to floats --
# this is important since we'll be doing a bunch of divisions
if boxes.dtype.kind == "i":
boxes = boxes.astype("float")
# initialize the list of picked indexes
pick = []
# grab the coordinates of the bounding boxes
x1 = boxes[:,0]
y1 = boxes[:,1]
x2 = boxes[:,2]
y2 = boxes[:,3]
# compute the area of the bounding boxes and sort the bounding
# boxes by the bottom-right y-coordinate of the bounding box
area = (x2 - x1 + 1) * (y2 - y1 + 1)
idxs = np.argsort(y2)
# keep looping while some indexes still remain in the indexes
# list
while len(idxs) > 0:
# grab the last index in the indexes list and add the
# index value to the list of picked indexes
last = len(idxs) - 1
i = idxs[last]
pick.append(i)
# find the largest (x, y) coordinates for the start of
# the bounding box and the smallest (x, y) coordinates
# for the end of the bounding box
xx1 = np.maximum(x1[i], x1[idxs[:last]])
yy1 = np.maximum(y1[i], y1[idxs[:last]])
xx2 = np.minimum(x2[i], x2[idxs[:last]])
yy2 = np.minimum(y2[i], y2[idxs[:last]])
# compute the width and height of the bounding box
w = np.maximum(0, xx2 - xx1 + 1)
h = np.maximum(0, yy2 - yy1 + 1)
# compute the ratio of overlap
overlap = (w * h) / area[idxs[:last]]
# delete all indexes from the index list that have
idxs = np.delete(idxs, np.concatenate(([last],
np.where(overlap > overlapThresh)[0])))
# return only the bounding boxes that were picked using the
# integer data type
return boxes[pick].astype("int")
def dist_to_px(dist, spacing):
""" distance in um (or rather same unit as the spacing) """
dist_px = int(round(dist / spacing))
return dist_px
def get_centerpoints(box, dist):
"""Returns centerpoints of box"""
return (box[0]+dist, box[1]+dist)
def point_to_box(x, y, size):
"""Convert centerpoint to bounding box of fixed size"""
return np.array([x-size, y-size, x+size, y+size])
def slide_nms(slide_path, wsa_path, tile_size):
"""Iterate over WholeSlideAnnotation and perform NMS. For this to properly work, tiles need to be larger than model inference patches."""
wsi = WholeSlideImage(slide_path, backend='asap')
wsa = WholeSlideAnnotation(wsa_path)
shape = wsi.shapes[0]
center_nms_points = []
for y_pos in range(0, shape[1], tile_size):
for x_pos in range(0, shape[0], tile_size):
wsa_patch = wsa.select_annotations(int(x_pos+tile_size//2), int(y_pos+tile_size//2), tile_size, tile_size)
if wsa_patch:
wsa_patch_coords = [point.coordinates for point in wsa_patch]
if len(wsa_patch_coords) < 2:
continue
boxes = np.array([point_to_box(x[0], x[1], 8) for x in wsa_patch_coords])
nms_boxes = non_max_suppression_fast(boxes, 0.7)
for box in nms_boxes:
center_nms_points.append(get_centerpoints(box, 8))
return center_nms_points
def to_wsd(points):
"""Convert list of coordinates into WSD points"""
new_points = []
for i, point in enumerate(points):
p = Point(
index=i,
label=Label("til", 1, color="blue"),
coordinates=[point],
)
new_points.append(p)
return new_points