extract_textpoint_slow.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. # Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Contains various CTC decoders."""
  15. from __future__ import absolute_import
  16. from __future__ import division
  17. from __future__ import print_function
  18. import cv2
  19. import math
  20. import numpy as np
  21. from itertools import groupby
  22. from skimage.morphology._skeletonize import thin
  23. def get_dict(character_dict_path):
  24. character_str = ""
  25. with open(character_dict_path, "rb") as fin:
  26. lines = fin.readlines()
  27. for line in lines:
  28. line = line.decode('utf-8').strip("\n").strip("\r\n")
  29. character_str += line
  30. dict_character = list(character_str)
  31. return dict_character
  32. def point_pair2poly(point_pair_list):
  33. """
  34. Transfer vertical point_pairs into poly point in clockwise.
  35. """
  36. pair_length_list = []
  37. for point_pair in point_pair_list:
  38. pair_length = np.linalg.norm(point_pair[0] - point_pair[1])
  39. pair_length_list.append(pair_length)
  40. pair_length_list = np.array(pair_length_list)
  41. pair_info = (pair_length_list.max(), pair_length_list.min(),
  42. pair_length_list.mean())
  43. point_num = len(point_pair_list) * 2
  44. point_list = [0] * point_num
  45. for idx, point_pair in enumerate(point_pair_list):
  46. point_list[idx] = point_pair[0]
  47. point_list[point_num - 1 - idx] = point_pair[1]
  48. return np.array(point_list).reshape(-1, 2), pair_info
  49. def shrink_quad_along_width(quad, begin_width_ratio=0., end_width_ratio=1.):
  50. """
  51. Generate shrink_quad_along_width.
  52. """
  53. ratio_pair = np.array(
  54. [[begin_width_ratio], [end_width_ratio]], dtype=np.float32)
  55. p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair
  56. p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair
  57. return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]])
  58. def expand_poly_along_width(poly, shrink_ratio_of_width=0.3):
  59. """
  60. expand poly along width.
  61. """
  62. point_num = poly.shape[0]
  63. left_quad = np.array(
  64. [poly[0], poly[1], poly[-2], poly[-1]], dtype=np.float32)
  65. left_ratio = -shrink_ratio_of_width * np.linalg.norm(left_quad[0] - left_quad[3]) / \
  66. (np.linalg.norm(left_quad[0] - left_quad[1]) + 1e-6)
  67. left_quad_expand = shrink_quad_along_width(left_quad, left_ratio, 1.0)
  68. right_quad = np.array(
  69. [
  70. poly[point_num // 2 - 2], poly[point_num // 2 - 1],
  71. poly[point_num // 2], poly[point_num // 2 + 1]
  72. ],
  73. dtype=np.float32)
  74. right_ratio = 1.0 + \
  75. shrink_ratio_of_width * np.linalg.norm(right_quad[0] - right_quad[3]) / \
  76. (np.linalg.norm(right_quad[0] - right_quad[1]) + 1e-6)
  77. right_quad_expand = shrink_quad_along_width(right_quad, 0.0, right_ratio)
  78. poly[0] = left_quad_expand[0]
  79. poly[-1] = left_quad_expand[-1]
  80. poly[point_num // 2 - 1] = right_quad_expand[1]
  81. poly[point_num // 2] = right_quad_expand[2]
  82. return poly
  83. def softmax(logits):
  84. """
  85. logits: N x d
  86. """
  87. max_value = np.max(logits, axis=1, keepdims=True)
  88. exp = np.exp(logits - max_value)
  89. exp_sum = np.sum(exp, axis=1, keepdims=True)
  90. dist = exp / exp_sum
  91. return dist
  92. def get_keep_pos_idxs(labels, remove_blank=None):
  93. """
  94. Remove duplicate and get pos idxs of keep items.
  95. The value of keep_blank should be [None, 95].
  96. """
  97. duplicate_len_list = []
  98. keep_pos_idx_list = []
  99. keep_char_idx_list = []
  100. for k, v_ in groupby(labels):
  101. current_len = len(list(v_))
  102. if k != remove_blank:
  103. current_idx = int(sum(duplicate_len_list) + current_len // 2)
  104. keep_pos_idx_list.append(current_idx)
  105. keep_char_idx_list.append(k)
  106. duplicate_len_list.append(current_len)
  107. return keep_char_idx_list, keep_pos_idx_list
  108. def remove_blank(labels, blank=0):
  109. new_labels = [x for x in labels if x != blank]
  110. return new_labels
  111. def insert_blank(labels, blank=0):
  112. new_labels = [blank]
  113. for l in labels:
  114. new_labels += [l, blank]
  115. return new_labels
  116. def ctc_greedy_decoder(probs_seq, blank=95, keep_blank_in_idxs=True):
  117. """
  118. CTC greedy (best path) decoder.
  119. """
  120. raw_str = np.argmax(np.array(probs_seq), axis=1)
  121. remove_blank_in_pos = None if keep_blank_in_idxs else blank
  122. dedup_str, keep_idx_list = get_keep_pos_idxs(
  123. raw_str, remove_blank=remove_blank_in_pos)
  124. dst_str = remove_blank(dedup_str, blank=blank)
  125. return dst_str, keep_idx_list
  126. def instance_ctc_greedy_decoder(gather_info,
  127. logits_map,
  128. keep_blank_in_idxs=True):
  129. """
  130. gather_info: [[x, y], [x, y] ...]
  131. logits_map: H x W X (n_chars + 1)
  132. """
  133. _, _, C = logits_map.shape
  134. ys, xs = zip(*gather_info)
  135. logits_seq = logits_map[list(ys), list(xs)] # n x 96
  136. probs_seq = softmax(logits_seq)
  137. dst_str, keep_idx_list = ctc_greedy_decoder(
  138. probs_seq, blank=C - 1, keep_blank_in_idxs=keep_blank_in_idxs)
  139. keep_gather_list = [gather_info[idx] for idx in keep_idx_list]
  140. return dst_str, keep_gather_list
  141. def ctc_decoder_for_image(gather_info_list, logits_map,
  142. keep_blank_in_idxs=True):
  143. """
  144. CTC decoder using multiple processes.
  145. """
  146. decoder_results = []
  147. for gather_info in gather_info_list:
  148. res = instance_ctc_greedy_decoder(
  149. gather_info, logits_map, keep_blank_in_idxs=keep_blank_in_idxs)
  150. decoder_results.append(res)
  151. return decoder_results
  152. def sort_with_direction(pos_list, f_direction):
  153. """
  154. f_direction: h x w x 2
  155. pos_list: [[y, x], [y, x], [y, x] ...]
  156. """
  157. def sort_part_with_direction(pos_list, point_direction):
  158. pos_list = np.array(pos_list).reshape(-1, 2)
  159. point_direction = np.array(point_direction).reshape(-1, 2)
  160. average_direction = np.mean(point_direction, axis=0, keepdims=True)
  161. pos_proj_leng = np.sum(pos_list * average_direction, axis=1)
  162. sorted_list = pos_list[np.argsort(pos_proj_leng)].tolist()
  163. sorted_direction = point_direction[np.argsort(pos_proj_leng)].tolist()
  164. return sorted_list, sorted_direction
  165. pos_list = np.array(pos_list).reshape(-1, 2)
  166. point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] # x, y
  167. point_direction = point_direction[:, ::-1] # x, y -> y, x
  168. sorted_point, sorted_direction = sort_part_with_direction(pos_list,
  169. point_direction)
  170. point_num = len(sorted_point)
  171. if point_num >= 16:
  172. middle_num = point_num // 2
  173. first_part_point = sorted_point[:middle_num]
  174. first_point_direction = sorted_direction[:middle_num]
  175. sorted_fist_part_point, sorted_fist_part_direction = sort_part_with_direction(
  176. first_part_point, first_point_direction)
  177. last_part_point = sorted_point[middle_num:]
  178. last_point_direction = sorted_direction[middle_num:]
  179. sorted_last_part_point, sorted_last_part_direction = sort_part_with_direction(
  180. last_part_point, last_point_direction)
  181. sorted_point = sorted_fist_part_point + sorted_last_part_point
  182. sorted_direction = sorted_fist_part_direction + sorted_last_part_direction
  183. return sorted_point, np.array(sorted_direction)
  184. def add_id(pos_list, image_id=0):
  185. """
  186. Add id for gather feature, for inference.
  187. """
  188. new_list = []
  189. for item in pos_list:
  190. new_list.append((image_id, item[0], item[1]))
  191. return new_list
  192. def sort_and_expand_with_direction(pos_list, f_direction):
  193. """
  194. f_direction: h x w x 2
  195. pos_list: [[y, x], [y, x], [y, x] ...]
  196. """
  197. h, w, _ = f_direction.shape
  198. sorted_list, point_direction = sort_with_direction(pos_list, f_direction)
  199. # expand along
  200. point_num = len(sorted_list)
  201. sub_direction_len = max(point_num // 3, 2)
  202. left_direction = point_direction[:sub_direction_len, :]
  203. right_dirction = point_direction[point_num - sub_direction_len:, :]
  204. left_average_direction = -np.mean(left_direction, axis=0, keepdims=True)
  205. left_average_len = np.linalg.norm(left_average_direction)
  206. left_start = np.array(sorted_list[0])
  207. left_step = left_average_direction / (left_average_len + 1e-6)
  208. right_average_direction = np.mean(right_dirction, axis=0, keepdims=True)
  209. right_average_len = np.linalg.norm(right_average_direction)
  210. right_step = right_average_direction / (right_average_len + 1e-6)
  211. right_start = np.array(sorted_list[-1])
  212. append_num = max(
  213. int((left_average_len + right_average_len) / 2.0 * 0.15), 1)
  214. left_list = []
  215. right_list = []
  216. for i in range(append_num):
  217. ly, lx = np.round(left_start + left_step * (i + 1)).flatten().astype(
  218. 'int32').tolist()
  219. if ly < h and lx < w and (ly, lx) not in left_list:
  220. left_list.append((ly, lx))
  221. ry, rx = np.round(right_start + right_step * (i + 1)).flatten().astype(
  222. 'int32').tolist()
  223. if ry < h and rx < w and (ry, rx) not in right_list:
  224. right_list.append((ry, rx))
  225. all_list = left_list[::-1] + sorted_list + right_list
  226. return all_list
  227. def sort_and_expand_with_direction_v2(pos_list, f_direction, binary_tcl_map):
  228. """
  229. f_direction: h x w x 2
  230. pos_list: [[y, x], [y, x], [y, x] ...]
  231. binary_tcl_map: h x w
  232. """
  233. h, w, _ = f_direction.shape
  234. sorted_list, point_direction = sort_with_direction(pos_list, f_direction)
  235. # expand along
  236. point_num = len(sorted_list)
  237. sub_direction_len = max(point_num // 3, 2)
  238. left_direction = point_direction[:sub_direction_len, :]
  239. right_dirction = point_direction[point_num - sub_direction_len:, :]
  240. left_average_direction = -np.mean(left_direction, axis=0, keepdims=True)
  241. left_average_len = np.linalg.norm(left_average_direction)
  242. left_start = np.array(sorted_list[0])
  243. left_step = left_average_direction / (left_average_len + 1e-6)
  244. right_average_direction = np.mean(right_dirction, axis=0, keepdims=True)
  245. right_average_len = np.linalg.norm(right_average_direction)
  246. right_step = right_average_direction / (right_average_len + 1e-6)
  247. right_start = np.array(sorted_list[-1])
  248. append_num = max(
  249. int((left_average_len + right_average_len) / 2.0 * 0.15), 1)
  250. max_append_num = 2 * append_num
  251. left_list = []
  252. right_list = []
  253. for i in range(max_append_num):
  254. ly, lx = np.round(left_start + left_step * (i + 1)).flatten().astype(
  255. 'int32').tolist()
  256. if ly < h and lx < w and (ly, lx) not in left_list:
  257. if binary_tcl_map[ly, lx] > 0.5:
  258. left_list.append((ly, lx))
  259. else:
  260. break
  261. for i in range(max_append_num):
  262. ry, rx = np.round(right_start + right_step * (i + 1)).flatten().astype(
  263. 'int32').tolist()
  264. if ry < h and rx < w and (ry, rx) not in right_list:
  265. if binary_tcl_map[ry, rx] > 0.5:
  266. right_list.append((ry, rx))
  267. else:
  268. break
  269. all_list = left_list[::-1] + sorted_list + right_list
  270. return all_list
  271. def generate_pivot_list_curved(p_score,
  272. p_char_maps,
  273. f_direction,
  274. score_thresh=0.5,
  275. is_expand=True,
  276. is_backbone=False,
  277. image_id=0):
  278. """
  279. return center point and end point of TCL instance; filter with the char maps;
  280. """
  281. p_score = p_score[0]
  282. f_direction = f_direction.transpose(1, 2, 0)
  283. p_tcl_map = (p_score > score_thresh) * 1.0
  284. skeleton_map = thin(p_tcl_map)
  285. instance_count, instance_label_map = cv2.connectedComponents(
  286. skeleton_map.astype(np.uint8), connectivity=8)
  287. # get TCL Instance
  288. all_pos_yxs = []
  289. center_pos_yxs = []
  290. end_points_yxs = []
  291. instance_center_pos_yxs = []
  292. pred_strs = []
  293. if instance_count > 0:
  294. for instance_id in range(1, instance_count):
  295. pos_list = []
  296. ys, xs = np.where(instance_label_map == instance_id)
  297. pos_list = list(zip(ys, xs))
  298. ### FIX-ME, eliminate outlier
  299. if len(pos_list) < 3:
  300. continue
  301. if is_expand:
  302. pos_list_sorted = sort_and_expand_with_direction_v2(
  303. pos_list, f_direction, p_tcl_map)
  304. else:
  305. pos_list_sorted, _ = sort_with_direction(pos_list, f_direction)
  306. all_pos_yxs.append(pos_list_sorted)
  307. # use decoder to filter backgroud points.
  308. p_char_maps = p_char_maps.transpose([1, 2, 0])
  309. decode_res = ctc_decoder_for_image(
  310. all_pos_yxs, logits_map=p_char_maps, keep_blank_in_idxs=True)
  311. for decoded_str, keep_yxs_list in decode_res:
  312. if is_backbone:
  313. keep_yxs_list_with_id = add_id(keep_yxs_list, image_id=image_id)
  314. instance_center_pos_yxs.append(keep_yxs_list_with_id)
  315. pred_strs.append(decoded_str)
  316. else:
  317. end_points_yxs.extend((keep_yxs_list[0], keep_yxs_list[-1]))
  318. center_pos_yxs.extend(keep_yxs_list)
  319. if is_backbone:
  320. return pred_strs, instance_center_pos_yxs
  321. else:
  322. return center_pos_yxs, end_points_yxs
  323. def generate_pivot_list_horizontal(p_score,
  324. p_char_maps,
  325. f_direction,
  326. score_thresh=0.5,
  327. is_backbone=False,
  328. image_id=0):
  329. """
  330. return center point and end point of TCL instance; filter with the char maps;
  331. """
  332. p_score = p_score[0]
  333. f_direction = f_direction.transpose(1, 2, 0)
  334. p_tcl_map_bi = (p_score > score_thresh) * 1.0
  335. instance_count, instance_label_map = cv2.connectedComponents(
  336. p_tcl_map_bi.astype(np.uint8), connectivity=8)
  337. # get TCL Instance
  338. all_pos_yxs = []
  339. center_pos_yxs = []
  340. end_points_yxs = []
  341. instance_center_pos_yxs = []
  342. if instance_count > 0:
  343. for instance_id in range(1, instance_count):
  344. pos_list = []
  345. ys, xs = np.where(instance_label_map == instance_id)
  346. pos_list = list(zip(ys, xs))
  347. ### FIX-ME, eliminate outlier
  348. if len(pos_list) < 5:
  349. continue
  350. # add rule here
  351. main_direction = extract_main_direction(pos_list,
  352. f_direction) # y x
  353. reference_directin = np.array([0, 1]).reshape([-1, 2]) # y x
  354. is_h_angle = abs(np.sum(
  355. main_direction * reference_directin)) < math.cos(math.pi / 180 *
  356. 70)
  357. point_yxs = np.array(pos_list)
  358. max_y, max_x = np.max(point_yxs, axis=0)
  359. min_y, min_x = np.min(point_yxs, axis=0)
  360. is_h_len = (max_y - min_y) < 1.5 * (max_x - min_x)
  361. pos_list_final = []
  362. if is_h_len:
  363. xs = np.unique(xs)
  364. for x in xs:
  365. ys = instance_label_map[:, x].copy().reshape((-1, ))
  366. y = int(np.where(ys == instance_id)[0].mean())
  367. pos_list_final.append((y, x))
  368. else:
  369. ys = np.unique(ys)
  370. for y in ys:
  371. xs = instance_label_map[y, :].copy().reshape((-1, ))
  372. x = int(np.where(xs == instance_id)[0].mean())
  373. pos_list_final.append((y, x))
  374. pos_list_sorted, _ = sort_with_direction(pos_list_final,
  375. f_direction)
  376. all_pos_yxs.append(pos_list_sorted)
  377. # use decoder to filter backgroud points.
  378. p_char_maps = p_char_maps.transpose([1, 2, 0])
  379. decode_res = ctc_decoder_for_image(
  380. all_pos_yxs, logits_map=p_char_maps, keep_blank_in_idxs=True)
  381. for decoded_str, keep_yxs_list in decode_res:
  382. if is_backbone:
  383. keep_yxs_list_with_id = add_id(keep_yxs_list, image_id=image_id)
  384. instance_center_pos_yxs.append(keep_yxs_list_with_id)
  385. else:
  386. end_points_yxs.extend((keep_yxs_list[0], keep_yxs_list[-1]))
  387. center_pos_yxs.extend(keep_yxs_list)
  388. if is_backbone:
  389. return instance_center_pos_yxs
  390. else:
  391. return center_pos_yxs, end_points_yxs
  392. def generate_pivot_list_slow(p_score,
  393. p_char_maps,
  394. f_direction,
  395. score_thresh=0.5,
  396. is_backbone=False,
  397. is_curved=True,
  398. image_id=0):
  399. """
  400. Warp all the function together.
  401. """
  402. if is_curved:
  403. return generate_pivot_list_curved(
  404. p_score,
  405. p_char_maps,
  406. f_direction,
  407. score_thresh=score_thresh,
  408. is_expand=True,
  409. is_backbone=is_backbone,
  410. image_id=image_id)
  411. else:
  412. return generate_pivot_list_horizontal(
  413. p_score,
  414. p_char_maps,
  415. f_direction,
  416. score_thresh=score_thresh,
  417. is_backbone=is_backbone,
  418. image_id=image_id)
  419. # for refine module
  420. def extract_main_direction(pos_list, f_direction):
  421. """
  422. f_direction: h x w x 2
  423. pos_list: [[y, x], [y, x], [y, x] ...]
  424. """
  425. pos_list = np.array(pos_list)
  426. point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]]
  427. point_direction = point_direction[:, ::-1] # x, y -> y, x
  428. average_direction = np.mean(point_direction, axis=0, keepdims=True)
  429. average_direction = average_direction / (
  430. np.linalg.norm(average_direction) + 1e-6)
  431. return average_direction
  432. def sort_by_direction_with_image_id_deprecated(pos_list, f_direction):
  433. """
  434. f_direction: h x w x 2
  435. pos_list: [[id, y, x], [id, y, x], [id, y, x] ...]
  436. """
  437. pos_list_full = np.array(pos_list).reshape(-1, 3)
  438. pos_list = pos_list_full[:, 1:]
  439. point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] # x, y
  440. point_direction = point_direction[:, ::-1] # x, y -> y, x
  441. average_direction = np.mean(point_direction, axis=0, keepdims=True)
  442. pos_proj_leng = np.sum(pos_list * average_direction, axis=1)
  443. sorted_list = pos_list_full[np.argsort(pos_proj_leng)].tolist()
  444. return sorted_list
  445. def sort_by_direction_with_image_id(pos_list, f_direction):
  446. """
  447. f_direction: h x w x 2
  448. pos_list: [[y, x], [y, x], [y, x] ...]
  449. """
  450. def sort_part_with_direction(pos_list_full, point_direction):
  451. pos_list_full = np.array(pos_list_full).reshape(-1, 3)
  452. pos_list = pos_list_full[:, 1:]
  453. point_direction = np.array(point_direction).reshape(-1, 2)
  454. average_direction = np.mean(point_direction, axis=0, keepdims=True)
  455. pos_proj_leng = np.sum(pos_list * average_direction, axis=1)
  456. sorted_list = pos_list_full[np.argsort(pos_proj_leng)].tolist()
  457. sorted_direction = point_direction[np.argsort(pos_proj_leng)].tolist()
  458. return sorted_list, sorted_direction
  459. pos_list = np.array(pos_list).reshape(-1, 3)
  460. point_direction = f_direction[pos_list[:, 1], pos_list[:, 2]] # x, y
  461. point_direction = point_direction[:, ::-1] # x, y -> y, x
  462. sorted_point, sorted_direction = sort_part_with_direction(pos_list,
  463. point_direction)
  464. point_num = len(sorted_point)
  465. if point_num >= 16:
  466. middle_num = point_num // 2
  467. first_part_point = sorted_point[:middle_num]
  468. first_point_direction = sorted_direction[:middle_num]
  469. sorted_fist_part_point, sorted_fist_part_direction = sort_part_with_direction(
  470. first_part_point, first_point_direction)
  471. last_part_point = sorted_point[middle_num:]
  472. last_point_direction = sorted_direction[middle_num:]
  473. sorted_last_part_point, sorted_last_part_direction = sort_part_with_direction(
  474. last_part_point, last_point_direction)
  475. sorted_point = sorted_fist_part_point + sorted_last_part_point
  476. sorted_direction = sorted_fist_part_direction + sorted_last_part_direction
  477. return sorted_point
  478. def generate_pivot_list_tt_inference(p_score,
  479. p_char_maps,
  480. f_direction,
  481. score_thresh=0.5,
  482. is_backbone=False,
  483. is_curved=True,
  484. image_id=0):
  485. """
  486. return center point and end point of TCL instance; filter with the char maps;
  487. """
  488. p_score = p_score[0]
  489. f_direction = f_direction.transpose(1, 2, 0)
  490. p_tcl_map = (p_score > score_thresh) * 1.0
  491. skeleton_map = thin(p_tcl_map)
  492. instance_count, instance_label_map = cv2.connectedComponents(
  493. skeleton_map.astype(np.uint8), connectivity=8)
  494. # get TCL Instance
  495. all_pos_yxs = []
  496. if instance_count > 0:
  497. for instance_id in range(1, instance_count):
  498. pos_list = []
  499. ys, xs = np.where(instance_label_map == instance_id)
  500. pos_list = list(zip(ys, xs))
  501. ### FIX-ME, eliminate outlier
  502. if len(pos_list) < 3:
  503. continue
  504. pos_list_sorted = sort_and_expand_with_direction_v2(
  505. pos_list, f_direction, p_tcl_map)
  506. pos_list_sorted_with_id = add_id(pos_list_sorted, image_id=image_id)
  507. all_pos_yxs.append(pos_list_sorted_with_id)
  508. return all_pos_yxs