basic_ops.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import cv2
  2. import numpy as np
  3. import math
  4. import torch
  5. from scipy.ndimage.morphology import distance_transform_edt
  6. class Line(object):
  7. def __init__(self, coordinates=[0, 0, 1, 1]):
  8. """
  9. coordinates: [y0, x0, y1, x1]
  10. """
  11. assert isinstance(coordinates, list)
  12. assert len(coordinates) == 4
  13. assert coordinates[0]!=coordinates[2] or coordinates[1]!=coordinates[3]
  14. self.__coordinates = coordinates
  15. @property
  16. def coord(self):
  17. return self.__coordinates
  18. @property
  19. def length(self):
  20. start = np.array(self.coord[:2])
  21. end = np.array(self.coord[2::])
  22. return np.sqrt(((start - end) ** 2).sum())
  23. def angle(self):
  24. y0, x0, y1, x1 = self.coord
  25. if x0 == x1:
  26. return -np.pi / 2
  27. return np.arctan((y0-y1) / (x0-x1))
  28. def rescale(self, rh, rw):
  29. coor = np.array(self.__coordinates)
  30. r = np.array([rh, rw, rh, rw])
  31. self.__coordinates = np.round(coor * r).astype(np.int).tolist()
  32. def __repr__(self):
  33. return str(self.coord)
  34. class LineAnnotation(object):
  35. def __init__(self, size, lines, divisions=12):
  36. # assert isinstance(lines, Line)
  37. # assert isinstance(size, Line)
  38. assert divisions > 1
  39. assert size[0] > 1 and size[1] > 1
  40. self.size = size
  41. self.divisions = divisions
  42. self.lines = lines
  43. # binary mask with shape [H, W]
  44. self.__mask = None
  45. # oriental mask with shape [ndivision, H, W]
  46. self.__oriental_mask = None
  47. # oriental mask only with angle [H, W]
  48. self.__angle_mask = None
  49. # regression label [distance_regression, oriental_regrression] with shape [H, W, 2] and [H, W, ndivision]
  50. self.__regression_label = None
  51. # the offset of non-line pixels to line pixels
  52. self.__offset = None
  53. def mask(self):
  54. if self.__mask is None:
  55. self.__mask = line2mask(self.size, self.lines)
  56. return self.__mask
  57. def oriental_mask(self):
  58. if self.__oriental_mask is None:
  59. mask = self.mask()
  60. oriental_mask_ = np.zeros([self.divisions] + self.size, np.uint8)
  61. angle_mask_ = np.zeros(self.size, np.uint8)
  62. for idx, l in enumerate(self.lines):
  63. mask1 = mask == (idx+1)
  64. orient = round(( l.angle() + np.pi/2 ) / (np.pi / self.divisions)) % self.divisions # 0, 1, ..., 11
  65. assert orient >= 0 and orient < self.divisions
  66. oriental_mask_[int(orient), mask1] = 1
  67. angle_mask_[mask1] = orient
  68. self.__oriental_mask = oriental_mask_
  69. self.__angle_mask = angle_mask_
  70. return self.__oriental_mask
  71. def angle_mask(self):
  72. if self.__angle_mask is None:
  73. angle_mask = self.oriental_mask
  74. return self.__angle_mask
  75. def regression_label(self):
  76. if self.__regression_label is None:
  77. # reg_oriental_label = np.zeros(self.size+[self.divisions], dtype=np.float)
  78. angle = np.zeros(self.size+[self.divisions]).reshape(-1, self.divisions)
  79. reg_distance_label = np.zeros(self.size+[2], dtype=np.float)
  80. orient = np.zeros(len(self.lines))
  81. dist_pre_line = np.zeros([len(self.lines)]+self.size)
  82. mask = self.mask()
  83. for idx, l in enumerate(self.lines):
  84. dist_pre_line[idx] = distance_transform_edt(mask != (idx+1))
  85. orient[idx] = l.angle()
  86. _, [indicesY, indicesX] = distance_transform_edt(mask==0, return_indices=True)
  87. dx = indicesX - np.tile(range(self.size[1]), (self.size[0], 1))
  88. dy = indicesY - np.tile(range(self.size[0]), (self.size[1], 1)).transpose()
  89. theta = orient[np.argmin(dist_pre_line, 0).reshape(-1)] # [H*W]
  90. angle[:] = [-np.pi/2 + k*np.pi / self.divisions for k in range(self.divisions)]
  91. d_theta = theta - angle.transpose()
  92. reg_oriental_label = d_theta.reshape([-1]+self.size).transpose()
  93. reg_distance_label[:,:,1] = dx
  94. reg_distance_label[:,:,0] = dy
  95. self.__regression_label = [reg_distance_label, reg_oriental_label]
  96. return self.__regression_label
  97. def offset(self):
  98. if self.__offset is None:
  99. mask = self.__mask.astype(bool)
  100. H, W = mask.shape
  101. bw_dist, bw_idx = distance_transform_edt(np.logical_not(mask), return_indices=True)
  102. tmp0 = np.arange(H).reshape(H, 1).repeat(W, 1).reshape(1, H, W)
  103. tmp1 = np.arange(W).reshape(1, W).repeat(H, 0).reshape(1, H, W)
  104. xys = np.concatenate((tmp0, tmp1), axis=0)
  105. offset = bw_idx - xys
  106. # check corectness
  107. x, y = np.random.choice(W), np.random.choice(H)
  108. assert np.sqrt((offset[:, y, x]**2).sum()) == bw_dist[y, x]
  109. return self.__offset
  110. def normed_offset(self):
  111. mask = self.__mask.astype(bool)
  112. bw_dist, _ = distance_transform_edt(np.logical_not(mask), return_indices=True)
  113. bw_dist[bw_dist == 0] = 1
  114. return self.offset() / bw_dist
  115. def rescale(self, r):
  116. """
  117. Downsample annotations
  118. """
  119. assert r > 0 and (isinstance(r, int) or isinstance(r, float))
  120. for l in self.lines:
  121. l.rescale(rh=1/r, rw=1/r)
  122. self.size = (np.array(self.size) / r).astype(np.int).tolist()
  123. self.__mask = None
  124. self.__oriental_mask = None
  125. self.__angle_mask = None
  126. self.__regression_label = None
  127. def resize(self, size):
  128. H, W = size
  129. rH = H / self.size[0]
  130. rW = W / self.size[1]
  131. self.size = [H, W]
  132. for l in self.lines:
  133. l.rescale(rh=rH, rw=rW)
  134. self.__mask = None
  135. self.__oriental_mask = None
  136. self.__angle_mask = None
  137. self.__regression_label = None
  138. def line2mask(size, lines):
  139. H, W = size
  140. mask = np.zeros((H, W), np.uint8)
  141. for idx, l in enumerate(lines):
  142. y0, x0, y1, x1 = l.coord
  143. cv2.line(mask, (x0, y0), (x1, y1), (idx+1), 2)
  144. return mask
  145. def get_boundary_point(y, x, angle, H, W):
  146. '''
  147. Given point y,x with angle, return a two point in image boundary with shape [H, W]
  148. return point:[x, y]
  149. '''
  150. point1 = None
  151. point2 = None
  152. if angle == -np.pi / 2:
  153. point1 = (x, 0)
  154. point2 = (x, H-1)
  155. elif angle == 0.0:
  156. point1 = (0, y)
  157. point2 = (W-1, y)
  158. else:
  159. k = np.tan(angle)
  160. if y-k*x >=0 and y-k*x < H: #left
  161. if point1 == None:
  162. point1 = (0, int(y-k*x))
  163. elif point2 == None:
  164. point2 = (0, int(y-k*x))
  165. if point2 == point1: point2 = None
  166. # print(point1, point2)
  167. if k*(W-1)+y-k*x >= 0 and k*(W-1)+y-k*x < H: #right
  168. if point1 == None:
  169. point1 = (W-1, int(k*(W-1)+y-k*x))
  170. elif point2 == None:
  171. point2 = (W-1, int(k*(W-1)+y-k*x))
  172. if point2 == point1: point2 = None
  173. # print(point1, point2)
  174. if x-y/k >= 0 and x-y/k < W: #top
  175. if point1 == None:
  176. point1 = (int(x-y/k), 0)
  177. elif point2 == None:
  178. point2 = (int(x-y/k), 0)
  179. if point2 == point1: point2 = None
  180. # print(point1, point2)
  181. if x-y/k+(H-1)/k >= 0 and x-y/k+(H-1)/k < W: #bottom
  182. if point1 == None:
  183. point1 = (int(x-y/k+(H-1)/k), H-1)
  184. elif point2 == None:
  185. point2 = (int(x-y/k+(H-1)/k), H-1)
  186. if point2 == point1: point2 = None
  187. # print(int(x-y/k+(H-1)/k), H-1)
  188. if point2 == None : point2 = point1
  189. return point1, point2
  190. # def proposal2line(y, x, angle, size, num_directions=12):
  191. # '''
  192. # y, x, angle are the proposal point and angle.
  193. # '''
  194. # assert angle >= 0 and angle < num_directions
  195. # H, W = size
  196. # angle = int2arc(angle, num_directions)
  197. # point1, point2 = get_boundary_point(y, x, angle, H, W)
  198. # if point1 == None or point2 == None:
  199. # print(y, x, angle, H, W)
  200. # return Line(coordinates=[point1[1], point1[0], point2[1], point2[0]])
  201. # def proposal2coords(proposal):
  202. # N, C, H, W = proposal.size()
  203. # proposal = proposal.detach().cpu().numpy()
  204. # batch_coords = []
  205. # for b in range(N):
  206. # indexs = np.argwhere(proposal[b, ...])
  207. # select_num = indexs.shape[0]
  208. # if select_num == 0:
  209. # batch_coords.append(None)
  210. # continue
  211. # coords = torch.zeros((select_num, 5))
  212. # for idx, (c, y, x) in enumerate(indexs):
  213. # (x1, y1), (x2, y2) = get_boundary_point(y, x, int2arc(c, 12), H, W)
  214. # coords[idx, 0] = float(x1)
  215. # coords[idx, 1] = float(y1)
  216. # coords[idx, 2] = float(x2)
  217. # coords[idx, 3] = float(y2)
  218. # coords = coords.cuda()
  219. # batch_coords.append(coords)
  220. # return batch_coords
  221. # def proposal2label_mapping(proposal, label):
  222. # N, C, H, W = proposal.size()
  223. # proposal = proposal.detach().cpu().numpy()
  224. # indexs = np.argwhere(proposal)
  225. # select_num = indexs.shape[0]
  226. # label_mapping = torch.zeros((select_num, 1))
  227. # for idx, (n, c, y, x) in enumerate(indexs):
  228. # label_mapping[idx, 0] = label[n, c, y, x]
  229. # label_mapping = label_mapping.to(label)
  230. # return label_mapping
  231. def int2arc(k, num_directions):
  232. '''
  233. convert int to arc system with num_directions division.
  234. '''
  235. return -np.pi / 2 + np.pi / num_directions * k
  236. def arc2int(theta, num_directions):
  237. '''
  238. convert arc system to int with num_directions division.
  239. '''
  240. return round(( theta + np.pi/2 ) / (np.pi / num_directions)) % num_directions