Beispiel #1
0
    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)
Beispiel #2
0
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)
Beispiel #3
0
 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
Beispiel #4
0
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)
Beispiel #5
0
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
Beispiel #6
0
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)
Beispiel #7
0
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)
Beispiel #8
0
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)
Beispiel #9
0
    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
Beispiel #10
0
        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)
Beispiel #11
0
"""

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')
Beispiel #12
0
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
Beispiel #13
0
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
Beispiel #14
0
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
Beispiel #15
0
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)
Beispiel #16
0
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)
Beispiel #17
0
def display_colormaps():
    for cmn in colormaps.colormap_names:
        vis = colormap_gradient(cmn)
        imvis.imshow(vis, f'Colormap {cmn}', wait_ms=-1)
Beispiel #18
0
    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)