import numpy as np import cv2 from matplotlib import pyplot as plt import scipy # For line interpolation: # Comment this out if not using 'get_interp_points' function from bresenham import bresenham def hsv_to_bgr(h, s, v): # Get RGB values c = v * s x = c * (1 - abs((h * 6) % 2 - 1)) m = v - c if h < 1/6: r, g, b = c, x, 0 elif h < 1/3: r, g, b = x, c, 0 elif h < 0.5: r, g, b = 0, c, x elif h < 2/3: r, g, b = 0, x, c elif h < 5/6: r, g, b = x, 0, c else: r, g, b = c, 0, x # Scale RGB values to 0-255 range and convert to integers r = int((r + m) * 255) g = int((g + m) * 255) b = int((b + m) * 255) return (b, g, r) def get_distinct_colors(n): huePartition = 1.0 / (n + 1) return (hsv_to_bgr(huePartition * value, 1.0, 1.0) for value in range(0, n)) def is_color(img): if img.ndim <=2: return False # img.ndim >=3 if img.shape[-1] == 1: return False return True def show_img(img, color='gray', is_bgr=False, title='', figsize=None, final_show=True): """Show image using plt.""" if figsize: if not isinstance(figsize, (tuple, list)): figsize = (figsize, figsize) plt.figure(figsize=figsize) if is_color(img) and is_bgr: img = img[...,::-1] params = {'cmap':color} if img.dtype == np.uint8: params.update({'vmin': 0, 'vmax': 255}) plt.imshow(img, **params) plt.title(title) if final_show: plt.show() return def draw_xrange(img, xrange): annot_img = img.copy() im_h, im_w = annot_img.shape[:2] annot_img = cv2.line(annot_img, (xrange[0], 0), (xrange[0], im_h), (0,0,255), thickness=1) annot_img = cv2.line(annot_img, (xrange[1], 0), (xrange[1], im_h), (0,0,255), thickness=1) return annot_img def get_xrange(bin_line_mask): """ bin_line_mask: np.ndarray => black and white binary mask of line black => background => 0 white => foregrond line pixel => 255 returns: (x_start, x_end) where x_start and x_end represent the starting and ending points for the binary line segment """ # print(bin_line_mask.sum(axis=0)) # np.save("problem_mask.npy", bin_line_mask) smooth_signal = scipy.signal.medfilt(bin_line_mask.sum(axis=0), kernel_size=5) # print(smooth_signal.shape) # print(smooth_signal) # print(np.nonzero(smooth_signal)) x_range = np.nonzero(smooth_signal) if len(x_range) and len(x_range[0]): # To handle cases with empty masks x_range = x_range[0][[0, -1]] else: x_range = None return x_range def get_kp(line_img, interval=10, x_range=None, get_num_lines=False, get_center=True): """ line_img: np.ndarray => black and white binary mask of line black => background => 0 white => foregrond line pixel => 255 interval: delta_x at which x,y points are sampled across the line_img x_range: Range of x values, [xmin, xmax), within which pred points (x,y) are to be sampled returns: a list [{'x': , 'y': }, ....] of line points found in the binary line_img """ im_h, im_w = line_img.shape[:2] kps = [] # delta = 2 if x_range is None: x_range = (0, im_w) # track the number of vertical binary components found at every x => estimate num lines num_comps = [] for x in range(x_range[0], x_range[1], interval): # get the corresponding white pixel in this column fg_y = [] fg_y_center = [] all_y_points = np.where(line_img[:, x] == 255)[0] if all_y_points.size != 0: fg_y.append(all_y_points[0]) y = all_y_points[0] n_comps = 1 for idx in range(1, len(all_y_points)): y_next = all_y_points[idx] # print(y, y_next) if abs(y_next - y) > 2: n_comps += 1 # break found b/w y_next and y, separate components if fg_y[-1] != y: # handle the case where (first component itself is broken, i.e found break at idx=1) fg_y_center.append(round(y + fg_y[-1])//2) fg_y.append(y) else: fg_y_center.append(y) fg_y.append(y_next) y = y_next # print(fg_y,'\n', fg_y_center, '\n') # print('last_point', y, y_next) if fg_y[-1] != y: # add the last point fg_y_center.append(round(y + fg_y[-1])//2) fg_y.append(y) else: fg_y_center.append(y) num_comps.append(n_comps) if (fg_y or fg_y_center) and (n_comps==1): if get_center: kps.extend([{'x':float(x), 'y':y} for y in fg_y_center]) else: kps.extend([{'x':float(x), 'y':y} for y in fg_y]) res = kps if get_num_lines: res = kps, int(np.percentile(num_comps, 85)) return res def draw_edge(img, edge): inter_points = get_interp_points(edge[0], edge[1]) # print(inter_points) # print(len(inter_points), inter_points) annot_img = draw_kps(img, array_to_points(inter_points), color=(255,0,0)) return annot_img def draw_kps(img, kps, color=(0,255,0), classes=None, **draw_options): if is_color(img): annot_img = img.copy() else: annot_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) if classes is None: classes = [0]*len(kps) color_map = {0: color} else: colors = list(get_distinct_colors(classes.max()+1)) color_map = dict(zip(range(classes.max()+1), colors)) # print(color_map) for idx, kp in enumerate(kps): options = dict(color=color_map[classes[idx]], markerType=cv2.MARKER_CROSS, markerSize=2, thickness=2, line_type=8) options.update(draw_options) annot_img = cv2.drawMarker(annot_img, (int(kp['x']), int(kp['y'])), **options) return annot_img def points_to_array(pred_ds): res = [] for line in pred_ds: line_arr = [] for pt in line: line_arr.append([pt['x'], pt['y']]) res.append(line_arr) return res # res = line_utils.draw_lines(img, points_to_array(pred_ds)) def draw_lines(img, lines, classes=None): if is_color(img): annot_img = img.copy() else: annot_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) if classes is None: classes = list(range(len(lines))) if len(classes): colors = list(get_distinct_colors(max(classes)+1)) color_map = dict(zip(range(max(classes)+1), colors)) # print('color_map:', color_map) for line_idx, line in enumerate(lines): options = dict(color=color_map[classes[line_idx]], thickness=2) drawing_lines = [] for pt_idx in range(len(line)-1): drawing_lines.append([line[pt_idx], line[pt_idx+1]]) annot_img = cv2.polylines(annot_img, np.array(drawing_lines), isClosed=False, **options) return annot_img # Get the line points that would lie between ptA and ptB, according to the bresenham algorithm def get_interp_points(ptA, ptB, thickness=1): # x_interp = np.arange(ptA[0], ptB[0]) # y_interp = np.interp(x_interp, [ptA[0], ptB[0]], [ptA[1], ptB[1]]).round().astype(int) points = [] delta_range = (-thickness//2, thickness//2) for delta in range(delta_range[0], delta_range[1]+1): points.extend(list(bresenham(ptA[0], ptA[1]+delta, ptB[0], ptB[1]+delta))) inter_points = np.array(points) # inter_points = np.stack([x_interp,y_interp], axis=-1) return inter_points def array_to_points(pts_arr): pts = [{'x': pt[0], 'y': pt[1]} for pt in pts_arr] return pts