def _show_progress(self, H): if self.full_reference_image is None: _logger.error('Full template image was not set, cannot show progress!') return pts = np.zeros((3, 4), dtype=float) pts[:, 0] = np.array([0, 0, 1], dtype=float) pts[:, 1] = np.array([self.width, 0, 1], dtype=float) pts[:, 2] = np.array([self.width, self.height, 1], dtype=float) pts[:, 3] = np.array([0, self.height, 1], dtype=float) ref_pts = pts.copy() ref_pts = prj.apply_projection(self.H0, ref_pts) if self.H_gt is not None: P = prj.matmul(self.H0, prj.matmul(np.linalg.inv(H), prj.matmul(np.linalg.inv(self.H0), prj.matmul(self.H_gt, self.H0)))) pts = prj.apply_projection(P, pts) vis = self.full_reference_image.copy() for i in range(4): pt1 = (int(ref_pts[0, i]), int(ref_pts[1, i])) pt2 = (int(ref_pts[0, (i+1) % 4]), int(ref_pts[1, (i+1) % 4])) vis = cv2.line(vis, pt1, pt2, (0, 255, 0), 3) if self.H_gt is not None: pt1 = (int(pts[0, i]), int(pts[1, i])) pt2 = (int(pts[0, (i+1) % 4]), int(pts[1, (i+1) % 4])) vis = cv2.line(vis, pt1, pt2, (255, 0, 255), 2) imvis.imshow(vis, title='Progress', wait_ms=10)
def hough_lines(img): g = imutils.grayscale(img) vis = img.copy() edges = cv2.Canny(g, 100, 200, apertureSize=3) #lines = cv2.HoughLines(edges,1,np.pi/180,50) # for rho,theta in lines[0]: # a = np.cos(theta) # b = np.sin(theta) # x0 = a*rho # y0 = b*rho # x1 = int(x0 + 1000*(-b)) # y1 = int(y0 + 1000*(a)) # x2 = int(x0 - 1000*(-b)) # y2 = int(y0 - 1000*(a)) # cv2.line(vis,(x1,y1),(x2,y2),(255,0,255),2) lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 20, minLineLength=50, maxLineGap=5) if lines is not None: for r in range(lines.shape[0]): # print(lines[r,:].shape, lines[r,:][0], lines[r,0,0], lines[r,:,:], lines[r,:]) x1, y1, x2, y2 = lines[r, :][0] # for x1, y1, x2, y2 in lines: cv2.line(vis, (x1, y1), (x2, y2), (255, 0, 255), 2) #imvis.imshow(edges, title='edges', wait_ms=10) imvis.imshow(vis, title='Hough', wait_ms=10)
def _warp_current_image(self, img, H): res = cv2.warpPerspective(img, prj.matmul(self.H0, H), (self.width, self.height), flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0)) if self.verbose: imvis.imshow(res, 'Current Warp', wait_ms=10) return res
def fld_lines(img): #FIXME requires opencv-contrib-python g = imutils.grayscale(img) fld = cv2.ximgproc.createFastLineDetector() lines = fld.detect(g) vis = img.copy() vis = fld.drawSegments(vis, lines) imvis.imshow(vis, title='FLD lines', wait_ms=10)
def _find_initial_grid_points_contours(preproc, transform, pattern_specs, det_params, vis=None): print('WARNING - FINDING INITIAL GRID POINTS BY CONTOURS IS DEPRECATED') pyutils.tic('initial grid estimate - contour') #TODO remove ctpl = pattern_specs.calibration_template # Alias coords_dst = points2numpy(ctpl.refpts_full_marker) coords_src = points2numpy(transform.marker_corners) H = cv2.getPerspectiveTransform(coords_src, coords_dst) if H is None: return None, vis h, w = ctpl.tpl_full.shape[:2] # OpenCV doc: finding contours is finding white objects from black background! warped_img = cv2.warpPerspective(preproc.wb, H, (w, h), cv2.INTER_CUBIC) warped_mask = cv2.warpPerspective( np.ones(preproc.wb.shape[:2], dtype=np.uint8), H, (w, h), cv2.INTER_NEAREST) cnts = cv2.findContours(warped_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cnts = cnts[0] if len(cnts) == 2 else cnts[1] vis_alt = imutils.ensure_c3(warped_img.copy()) idx = 0 expected_circle_area = (pattern_specs.calibration_template.dia_circle_px / 2)**2 * np.pi exp_circ_area_lower = 0.5 * expected_circle_area exp_circ_area_upper = 2 * expected_circle_area for shape in cnts: area = cv2.contourArea(shape) if area < exp_circ_area_lower or area > exp_circ_area_upper: color = (255, 0, 0) else: color = (0, 0, 255) # continue # Centroid M = cv2.moments(shape) try: cx = np.round(M['m10'] / M['m00']) cy = np.round(M['m01'] / M['m00']) except ZeroDivisionError: continue idx += 1 if det_params.debug: cv2.drawContours(vis_alt, [shape], 0, color, -1) cv2.circle(vis_alt, (int(cx), int(cy)), 1, (255, 255, 0), -1) if idx % 10 == 0: imvis.imshow(vis_alt, 'Points by contour', wait_ms=10) if det_params.debug: imvis.imshow(vis_alt, 'Points by contour', wait_ms=10) initial_estimates = list() #TODO match the points #TODO draw debug on vis pyutils.toc('initial grid estimate - contour') #TODO remove return initial_estimates, vis
def mser(img): g = imutils.grayscale(img) vis = img.copy() mser = cv2.MSER_create(_min_area=1000) regions, _ = mser.detectRegions(g) for p in regions: xmax, ymax = np.amax(p, axis=0) xmin, ymin = np.amin(p, axis=0) cv2.rectangle(vis, (xmin, ymax), (xmax, ymin), (0, 255, 0), 1) imvis.imshow(vis, title='mser', wait_ms=-1)
def demo(): #TODO separate assets folder, use abspath img = imutils.imread('flamingo.jpg') rect = (180, 170, 120, 143) target_template = imutils.roi(img, rect) imvis.imshow(target_template, 'Template', wait_ms=10) warped, H_gt = _generate_warped_image(img, -45, -25, 20, 30, -30, -360) imvis.imshow(warped, 'Simulated Warp', wait_ms=10) # Initial estimate H0 H0 = np.eye(3, dtype=float) H0[0, 2] = rect[0] H0[1, 2] = rect[1] _logger.info(f'Initial estimate, H0:\n{H0}') # print('H0\n', H0) # print('H_gt\n', H_gt) verbose = True pyutils.tic('FC') align = Alignment(target_template, Method.FC, full_reference_image=img, num_pyramid_levels=5, verbose=verbose) align.set_true_warp(H_gt) H_est, result = align.align(warped, H0) pyutils.toc('FC') imvis.imshow(result, 'Result FC', wait_ms=10) pyutils.tic('IC') align = Alignment(target_template, Method.IC, full_reference_image=img, num_pyramid_levels=3, verbose=verbose) align.set_true_warp(H_gt) H_est, result = align.align(warped, H0) pyutils.toc('IC') imvis.imshow(result, 'Result IC', wait_ms=10) pyutils.tic('ESM') align = Alignment(target_template, Method.ESM, full_reference_image=img, num_pyramid_levels=5, verbose=verbose) align.set_true_warp(H_gt) H_est, result = align.align(warped, H0) pyutils.toc('ESM') imvis.imshow(result, 'Result ESM', wait_ms=-1)
def contour(img): g = imutils.grayscale(img) _, g = cv2.threshold(g, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # g = imutils.gaussian_blur(g, 1) # g = cv2.blur(g, (3,3)) vis = imutils.ensure_c3(g) vis = img.copy() edges = cv2.Canny(g, 50, 200, apertureSize=3) kernel = np.ones((3, 3), np.uint8) edges = cv2.dilate(edges, kernel, iterations=1) cnts = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) #cv2.CHAIN_APPROX_SIMPLE) cnts = cnts[0] if len(cnts) == 2 else cnts[1] #https://docs.opencv.org/3.4/d4/d73/tutorial_py_contours_begin.html # White on black! approximated_polygons = list() for cnt in cnts: # get simplified convex hull epsilon = 0.05 * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) hull = cv2.convexHull(approx) # cv2.drawContours(vis, [approx], 0, (0,255,0), 3) approximated_polygons.append({ 'hull': hull, 'approx': approx, 'hull_area': cv2.contourArea(hull), 'corners': len(hull) }) def _key(a): return a['hull_area'] approximated_polygons.sort(key=_key, reverse=True) i = 0 for approx in approximated_polygons: #cv2.drawContours(vis, [approx['cnt']], 0, (0,255,0) if i < 10 else (255,0,0), 3) cv2.drawContours(vis, [approx['hull']], 0, (0, 255, 0) if 3 < approx['corners'] < 6 else (255, 0, 0), 3) i += 1 # if i < 15: # print('Largest', i, approx['approx'], approx['area']) # imvis.imshow(vis, title='contours', wait_ms=-1) imvis.imshow(vis, title='contours', wait_ms=-1)
def align(self, image, H0): self.H0 = H0 if self.verbose: vis = self._warp_current_image(image, np.eye(3)) imvis.imshow(vis, 'Initial Warp', wait_ms=10) curr_original_image = image.copy() working_image = imutils.grayscale(image) working_image = cv2.GaussianBlur(working_image, self.blur_kernel_size, 0) working_pyramid = img_utils.image_pyramid(working_image, self.num_pyramid_levels) H = np.eye(3, dtype=float) # Coarse-to-fine: for lvl in range(self.num_pyramid_levels): pyr_lvl = self.num_pyramid_levels - lvl - 1 H = self._process_in_layer(H, working_pyramid[pyr_lvl], self.template_pyramid[pyr_lvl], pyr_lvl) warped = self._warp_current_image(curr_original_image, H) return H, warped
AttributeDict({ 'alias': sname2, 'abbreviation': 'S2', 'sec': sensor2_sec, 'val': sensor2_val }), sname3: AttributeDict({ 'alias': sname3, 'abbreviation': 'S3', 'sec': [[1800]], # Half an hour after dt_from 'val': [[27]] }) } return dt_from, dt_to, heating_series, sensor_series if __name__ == '__main__': dt_from, dt_to, heating_series, sensor_series = get_demo_series2() img = _plot_temperature_series(sensor_series, heating_series, dt_from, dt_to, width_px=1024, height_px=768, xkcdify=True, font_size=14, language=None) from vito import imvis imvis.imshow(img)
""" import os import sys # Extend the python path sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) from vito import colormaps from vito import flowutils from vito import imutils from vito import imvis if __name__ == "__main__": # Standard loading/display rgb = imutils.imread('flamingo.jpg', mode='RGB') imvis.imshow(rgb) # Load as BGR bgr = imutils.imread('flamingo.jpg', mode='RGB', flip_channels=True) imvis.imshow(bgr) # Load a single-channel image peaks = imutils.imread('peaks.png', mode='L') # Colorize it colorized = imvis.pseudocolor(peaks, limits=None, color_map=colormaps.colormap_viridis_rgb) imvis.imshow(colorized) # Load optical flow and visualize it flow_uv = flowutils.floread('color_wheel.flo')
def _find_initial_grid_points_correlation(preproc, transform, pattern_specs, det_params, vis=None): pyutils.tic('initial grid estimate - correlation') #TODO remove ctpl = pattern_specs.calibration_template # Alias coords_dst = points2numpy(ctpl.refpts_full_marker) coords_src = points2numpy(transform.marker_corners) H = cv2.getPerspectiveTransform(coords_src, coords_dst) if H is None: return None, vis h, w = ctpl.tpl_full.shape[:2] warped_img = cv2.warpPerspective(preproc.bw, H, (w, h), cv2.INTER_CUBIC) warped_mask = cv2.warpPerspective( np.ones(preproc.bw.shape[:2], dtype=np.uint8), H, (w, h), cv2.INTER_NEAREST) ncc = cv2.matchTemplate( warped_img, ctpl.tpl_cropped_circle, cv2.TM_CCOEFF_NORMED) # mask must be template size?? ncc[ncc < det_params.grid_ccoeff_thresh_initial] = 0 if det_params.debug: overlay = imutils.ensure_c3( imvis.overlay(ctpl.tpl_full, 0.3, warped_img, warped_mask)) warped_img_corners = pru.apply_projection( H, points2numpy(image_corners(preproc.bw), Nx2=False)) for i in range(4): pt1 = numpy2cvpt(warped_img_corners[:, i]) pt2 = numpy2cvpt(warped_img_corners[:, (i + 1) % 4]) cv2.line(overlay, pt1, pt2, color=(0, 0, 255), thickness=3) #FIXME FIXME FIXME # Idea: detect blobs in thresholded NCC # this could replace the greedy nms below # barycenter/centroid of each blob gives the top-left corner (then compute the relative offset to get the initial corner guess) initial_estimates = list() tpl = ctpl.tpl_cropped_circle while True: y, x = np.unravel_index(ncc.argmax(), ncc.shape) # print('Next', y, x, ncc[y, x], det_params.grid_ccoeff_thresh_initial, ncc.shape) if ncc[y, x] < det_params.grid_ccoeff_thresh_initial: break initial_estimates.append( CalibrationGridPoint(x=x, y=y, score=ncc[y, x])) # Clear the NCC peak around the detected template left = x - tpl.shape[1] // 2 top = y - tpl.shape[0] // 2 left, top, nms_w, nms_h = imutils.clip_rect_to_image( (left, top, tpl.shape[1], tpl.shape[0]), ncc.shape[1], ncc.shape[0]) right = left + nms_w bottom = top + nms_h ncc[top:bottom, left:right] = 0 if det_params.debug: cv2.rectangle(overlay, (x, y), (x + ctpl.tpl_cropped_circle.shape[1], y + ctpl.tpl_cropped_circle.shape[0]), (255, 0, 255)) if len(initial_estimates) % 20 == 0: # imvis.imshow(imvis.pseudocolor(ncc, [-1, 1]), 'NCC Result', wait_ms=10) imvis.imshow(overlay, 'Points by correlation', wait_ms=10) if vis is not None: cv2.drawContours(vis, [transform.shape['hull']], 0, (200, 0, 200), 3) if det_params.debug: print('Check "Points by correlation". Press key to continue') imvis.imshow(overlay, 'Points by correlation', wait_ms=-1) pyutils.toc('initial grid estimate - correlation') #TODO remove return initial_estimates, vis
def _md_find_center_marker_candidates(det_params, preprocessed, vis_img=None): """Locate candidate regions which could contain the marker.""" debug_shape_extraction = det_params.debug and True # We don't want hierarchies of contours here, just the largest (i.e. the # root) contour of each hierarchy is fine: cnts = cv2.findContours(preprocessed.wb, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = cnts[0] if len(cnts) == 2 else cnts[1] # Collect the convex hulls of all detected contours shapes = list() if debug_shape_extraction: tmp_vis = imutils.ensure_c3(preprocessed.wb) tmp_drawn = 0 for cnt in cnts: # Compute a simplified convex hull epsilon = det_params.simplification_factor * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) # Important: # Simplification with too large epsilons could lead to intersecting # polygons. These would cause incorrect area computation, and more # "fun" behavior. Thus, we work with the shape's convex hull from # now on. hull = cv2.convexHull(approx) area = cv2.contourArea(hull) if debug_shape_extraction: cv2.drawContours(tmp_vis, [cnt], 0, (255, 0, 0), 7) cv2.drawContours(tmp_vis, [hull], 0, (255, 0, 255), 7) tmp_drawn += 1 if tmp_drawn % 10 == 0: imvis.imshow(tmp_vis, 'Shape candidates', wait_ms=10) if det_params.min_marker_area_px is None or\ area >= det_params.min_marker_area_px: shapes.append({ 'hull': hull, 'approx': approx, 'cnt': cnt, 'hull_area': area, 'num_corners': len(hull) }) if debug_shape_extraction: print('Check "shape candidates". Press key to continue') imvis.imshow(tmp_vis, 'Shape candidates', wait_ms=-1) # Sort candidate shapes by area (descending) shapes.sort(key=lambda s: s['hull_area'], reverse=True) # Collect valid convex hulls, i.e. having 4-6 corners which could # correspond to a rectangular region. candidate_shapes = list() for shape in shapes: is_candidate = False if 3 < shape['num_corners'] <= 8: candidate = _ensure_quadrilateral( shape ) #TODO pass image for debug visualizations, preprocessed.original) if candidate is not None: is_candidate = True candidate_shapes.append(candidate) if vis_img is not None: cv2.drawContours(vis_img, [shape['hull']], 0, (0, 255, 0) if is_candidate else (255, 0, 0), 7) if det_params.max_candidates_per_image > 0 and det_params.max_candidates_per_image <= len( candidate_shapes): logging.info( f'Reached maximum amount of {det_params.max_candidates_per_image} candidate shapes.' ) break return candidate_shapes, vis_img
def _ensure_quadrilateral(shape, img=None): """Returns a 4-corner approximation of the given shape.""" if shape['num_corners'] < 4 or shape['num_corners'] > 8: return None if shape['num_corners'] == 4: return shape #TODO is there a robust way to approximate a quad via line intersection? # what if the longest edge is not on the opposite side of the clipping image border/occluder? # # TODO as of now, this is just a nice-to-have functionality (lowest priority) # # Assumption: the longest edge is fully visible - TODO verify if both endpoints are within # the image (not touching the border? but then there`could be an occluding object...) but such # bad examples wouldn't pass the NCC check anyhow.... # # Find the longest edge pts = [Point(x=pt[0, 0], y=pt[0, 1]) for pt in shape['hull']] edges = [ Edge(pts[idx], pts[(idx + 1) % len(pts)]) for idx in range(len(pts)) ] edge_lengths = np.array([e.length for e in edges]) idx_longest = np.argmax(edge_lengths) # Find most parallel edge most_parallel_angle = None idx_parallel = None orth_edges = list() for idx in range(len(edges)): ##### print('INTERSECTION DEMO: angle: ', edges[idx_longest].angle(edges[idx]), 'intersect:', edges[idx_longest].intersection(edges[idx])) if idx == idx_longest: continue angle = edges[idx_longest].angle(edges[idx]) orth_edges.append((edges[idx], np.abs(angle - np.pi / 2))) angle = np.abs(angle - np.pi) if most_parallel_angle is None or angle < most_parallel_angle: most_parallel_angle = angle idx_parallel = idx ##### print('Angle longest to {}: {} deg, idx: {}'.format(edges[idx], np.rad2deg(edges[idx_longest].angle(edges[idx])), idx_parallel)) # Sort edges by "how orthogonal they are" w.r.t. to the longest edge orth_edges.sort(key=lambda oe: oe[1]) # Remove the sorting key orth_edges = [oe[0] for oe in orth_edges] # Intersect the lines (longest & parallel with the two "most orthogonal") to # get the quad intersections = list() for pidx in [idx_longest, idx_parallel]: for oidx in [0, 1]: ip = edges[pidx].intersection(orth_edges[oidx]) if ip is not None: intersections.append(ip) # Sort the intersection points in CCW order to get a convex hull, starting # from the bottommost point intersections = sort_points_ccw(intersections, pt_ref=bottommost_point(intersections)) # Debug visualizations if img is not None: vis = img.copy() cv2.line(vis, edges[idx_longest].pt1.int_repr(), edges[idx_longest].pt2.int_repr(), (255, 0, 0), 3) cv2.line(vis, edges[idx_parallel].pt1.int_repr(), edges[idx_parallel].pt2.int_repr(), (255, 255, 0), 3) for idx in range(2): cv2.line(vis, orth_edges[idx].pt1.int_repr(), orth_edges[idx].pt2.int_repr(), (0, 255, 255), 3) for ip in intersections: cv2.circle(vis, ip.int_repr(), 10, (255, 0, 255), 3) imvis.imshow( vis, 'Ensure quad: r=longest, y=parallel, c=orth, m=intersections', wait_ms=100) # Convert intersection points to same format as OpenCV uses for contours hull = np.zeros((len(intersections), 1, 2), dtype=np.int32) for idx in range(len(intersections)): hull[idx, 0, :] = intersections[idx].int_repr() shape['hull'] = hull shape['num_corners'] = len(intersections) # Unnecessary check for now - but we might change the quad approximation # later on, so for future safety, verify the number of hull points again: if shape['num_corners'] == 4: return shape return None
def imshow(img, title="Image", wait_ms=10, flip_channels=False): return vimvis.imshow(img, title=title, wait_ms=wait_ms, flip_channels=flip_channels)
from vito import flowutils from vito import imvis # Load optical flow file flow = flowutils.floread('flow/out_00000_middlebury.flo') print(flow) # Colorize it colorized = flowutils.colorize_flow(flow) imvis.imshow(colorized)
def display_colormaps(): for cmn in colormaps.colormap_names: vis = colormap_gradient(cmn) imvis.imshow(vis, f'Colormap {cmn}', wait_ms=-1)
def _compute_reference_grid(self): #TODO doc debug = True num_refpts_per_row = self.circles_per_row - 1 num_refpts_per_col = self.circles_per_col - 1 if debug: from vito import imvis, imutils import cv2 vis = imutils.ensure_c3(self.calibration_template.tpl_full.copy()) visited = np.zeros((num_refpts_per_col, num_refpts_per_row), dtype=np.bool) nodes_to_visit = deque() nodes_to_visit.append(self._make_reference_point(0, 0)) nnr = 0 visible_count = 0 reference_points = list() while nodes_to_visit: n = nodes_to_visit.popleft() vidx = self._refpt2posgrid(n.col, n.row) if vidx is None or visited[vidx.row, vidx.col]: continue nnr += 1 visited[vidx.row, vidx.col] = True # # #circle test 31.03. bad idea # # if n.neighbor_dir is None or n.neighbor_dir == 0: # # nodes_to_visit.append(self._make_reference_point(col=n.col, row=n.row-1, neighbor_dir=0)) # # nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row-1, neighbor_dir=0)) # # if n.neighbor_dir is None or n.neighbor_dir == 1: # # nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row, neighbor_dir=1)) # # nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row+1, neighbor_dir=1)) # # if n.neighbor_dir is None or n.neighbor_dir == 2: # # nodes_to_visit.append(self._make_reference_point(col=n.col, row=n.row+1, neighbor_dir=2)) # # nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row+1, neighbor_dir=2)) # # if n.neighbor_dir is None or n.neighbor_dir == 3: # # nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row, neighbor_dir=3)) # # nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row-1, neighbor_dir=3)) ## 8-neighborhood (works okayish 31.03) nodes_to_visit.append( self._make_reference_point(col=n.col, row=n.row - 1)) nodes_to_visit.append( self._make_reference_point(col=n.col - 1, row=n.row - 1)) nodes_to_visit.append( self._make_reference_point(col=n.col - 1, row=n.row)) nodes_to_visit.append( self._make_reference_point(col=n.col - 1, row=n.row + 1)) nodes_to_visit.append( self._make_reference_point(col=n.col, row=n.row + 1)) nodes_to_visit.append( self._make_reference_point(col=n.col + 1, row=n.row + 1)) nodes_to_visit.append( self._make_reference_point(col=n.col + 1, row=n.row)) nodes_to_visit.append( self._make_reference_point(col=n.col + 1, row=n.row - 1)) # ## 4-neighborhood # # nodes_to_visit.append(self._make_reference_point(col=n.col, row=n.row-1)) # # nodes_to_visit.append(self._make_reference_point(col=n.col-1, row=n.row)) # # nodes_to_visit.append(self._make_reference_point(col=n.col, row=n.row+1)) # # nodes_to_visit.append(self._make_reference_point(col=n.col+1, row=n.row)) if n.surrounding_circles is not None: reference_points.append(n) if debug: pt = self._mm2px(n.pos_mm_tl) # cv2.putText(vis, f'{nnr:d}', pt.int_repr(), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), 1) cv2.putText(vis, f'{len(reference_points)-1:d}', pt.int_repr(), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), 1) cv2.circle(vis, pt.int_repr(), 3, (255, 0, 0), -1) imvis.imshow(vis, "Reference Grid", wait_ms=1) object.__setattr__(self, 'reference_points', reference_points) if debug: print('Check "reference grid". Press key to continue.') imvis.imshow(vis, "Reference Grid", wait_ms=-1)