1

I am working with some biological imaging samples and trying to create a digital model of the cell shapes. For the sake of simplicity I would like to generalize their shape by modelling them as polygons.

I am struggling to split two overlapping polygons into polygons that do not share the overlapping area at their intersections. Instead this area is divided between two shapes. My intentions are best illustrated below.

I work in Python, and OpenCV package but would be happy to implement any alternative packages that could solve this. (numpythonic way would be best - if possible!)

enter image description here

Kookaburra
  • 61
  • 1
  • 8
  • 1
    Seems conceptually simple - add the intersection points to each polygon, then eliminate all the points that are inside both polygons. – Mark Ransom Aug 16 '22 at 19:40
  • Note that a circle is not a polygon... If you want something more general, you could proceed with flood filling both shapes independently, and then each pixel that is reached from both shapes is removed. This works for any shape, no matter if they cross or not (just be careful about angles), but is more expensive and requires storing shapes by boundaries (not by equations). – jthulhu Aug 16 '22 at 19:53
  • "I am struggling to split two overlapping polygons into polygons that share the overlapping area at their intersections" I dont understand that. Your sample images dont SHARE the overlapping area. Instead, the overlap is REMOVED. If the removal is the right way, do you want 2 separate contours which dont overlap. or do you want a single contour that convers both regions (whithout duplicated overlap)? – Micka Aug 17 '22 at 11:05
  • 1
    @Micka Thanks - I edited the problem description. I would like to remove the overlap and end up with two polygons that have the overlapping area divided between them. – Kookaburra Aug 17 '22 at 13:25
  • @BlackBeans The circles is just a simplification of the cell shape (I am a biologist). The input data are two polygons, and I would like to end up with two updated polygons as an output. Can this be done without flood filling, using just arrays of polygon vertices? – Kookaburra Aug 17 '22 at 13:27

1 Answers1

5
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import cv2


threshold = 200
thickness = 16
linewidth = 2
white = 255
black = 0


def fun(image):
    res = np.full(image.shape, white).astype(np.uint8)
    # determine contours
    grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(grayscale, threshold, white, cv2.THRESH_BINARY)[1]
    contours = cv2.findContours(thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)[0]
    # create intersection and union
    intersection, union = sorted(contours, key=len)[::len(contours) - 2]
    outline = np.full(res.shape, white).astype(np.uint8)
    cv2.drawContours(outline, [union], -1, black, thickness, cv2.LINE_AA)
    inline = np.full(res.shape, white).astype(np.uint8)
    cv2.drawContours(inline, [intersection], -1, black, thickness, cv2.LINE_AA)
    # determine points for line
    points = np.logical_and(outline == 0, inline == 0).astype(np.uint8)[:,:,0] * white
    a, b = cv2.findContours(points, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)[0]
    a = np.mean(a.squeeze(), axis=0).astype(int)
    b = np.mean(b.squeeze(), axis=0).astype(int)
    # draw outline of union
    cv2.drawContours(res, [union], -1, black, linewidth, cv2.LINE_AA)
    # draw connecting line
    cv2.line(res, a, b, black, linewidth)
    return res

for img in ['img1.jpg', 'img2.jpg']:
    orig = np.asarray(Image.open(img))
    res = fun(orig)
    fig, ax = plt.subplots(1, 2)
    for i, img in enumerate([orig, res]):
        ax[i].imshow(img, cmap='gray');
        ax[i].axes.get_xaxis().set_visible(False)
        ax[i].axes.get_yaxis().set_visible(False)
    plt.show();

img1 img2

This approach works as follows:

  1. First convert the image to grayscale and use thresholding to binarize the image and then determine the contours. Next, sort the contours by increasing size, which ensures that the contour of the intersection (part to remove) comes first, the contour of the union (part to keep) comes second to last (last comes the contour of the image borders).
  2. Create two new white images, draw the contour of the union outline in black on one and the contour of the intersection inline in black on the other and then intersect the two images' black parts to get the patches marking the endpoints for the new line. Then compute the endpoints a and b for the new line as the mean of their respective patches.
  3. Draw a straight line connecting the two points a and b as well as the outline of the union on a new, initially white, image, in black, resulting in the desired output image.

Remark: This approach does not work for the third scenario (as it assumes the shapes to be convex, or much rather, to have only one intersection), but my guess is that adapting it to also work for such cases should be doable.

Michael Hodel
  • 2,845
  • 1
  • 5
  • 10