Skip to content

Instantly share code, notes, and snippets.

@evanthebouncy
Created May 5, 2024 22:01
Show Gist options
  • Save evanthebouncy/9f3df9b5d3194b71ece9ce9126e9be2d to your computer and use it in GitHub Desktop.
Save evanthebouncy/9f3df9b5d3194b71ece9ce9126e9be2d to your computer and use it in GitHub Desktop.
rendering a 3 point arc in openCV2 . . . I do not wish what I did upon anyone
import numpy as np
import matplotlib.pyplot as plt
import json
import cv2
import math
# open the ./data_train.json
GRID = 64
IMG_W = GRID * 20
# center and radius of circle, bug free
def find_circle(x1, y1, x2, y2, x3, y3):
# If the first point coincides with the third point, it is a full circle
if x1 == x3 and y1 == y3:
# Return the midpoint of the first and second point
ret = [(x1 + x2) / 2, (y1 + y2) / 2]
print(ret)
return ret
x12 = (x1 - x2)
x13 = (x1 - x3)
y12 = (y1 - y2)
y13 = (y1 - y3)
y31 = (y3 - y1)
y21 = (y2 - y1)
x31 = (x3 - x1)
x21 = (x2 - x1)
# x1^2 - x3^2
sx13 = x1**2 - x3**2
# y1^2 - y3^2
sy13 = y1**2 - y3**2
sx21 = x2**2 - x1**2
sy21 = y2**2 - y1**2
f = (sx13 * x12 + sy13 * x12 + sx21 * x13 + sy21 * x13) / (2 * (y31 * x12 - y21 * x13))
g = (sx13 * y12 + sy13 * y12 + sx21 * y13 + sy21 * y13) / (2 * (x31 * y12 - x21 * y13))
c = -(x1**2) - y1**2 - 2 * g * x1 - 2 * f * y1
# Equation of circle: x^2 + y^2 + 2*g*x + 2*f*y + c = 0
# where the center is (h = -g, k = -f) and radius r
# as r^2 = h^2 + k^2 - c
h = -g
k = -f
sqr_of_r = h**2 + k**2 - c
# r is the radius
r = math.sqrt(sqr_of_r)
return (h, k), r
def render_3pt_arc(entity, img, color, line_width=3):
# get and transform the 3 points
pt_start, pt_mid, pt_end = entity
pt_start = transform(pt_start[0], pt_start[1])
pt_mid = transform(pt_mid[0], pt_mid[1])
pt_end = transform(pt_end[0], pt_end[1])
# check if they are colinear, if they are, raise error
term1 = (pt_end[1] - pt_start[1]) * (pt_mid[0] - pt_start[0])
term2 = (pt_mid[1] - pt_start[1]) * (pt_end[0] - pt_start[0])
if (term1 - term2)**2 < 1e-3:
raise Colinear
center, _ = find_circle(pt_start[0], pt_start[1], pt_mid[0], pt_mid[1], pt_end[0], pt_end[1])
# radius is distance from center to start point
radius = math.sqrt((pt_start[0] - center[0])**2 + (pt_start[1] - center[1])**2)
# get the start angle
start_angle = np.arctan2(pt_start[1] - center[1], pt_start[0] - center[0])
# get the end angle
end_angle = np.arctan2(pt_end[1] - center[1], pt_end[0] - center[0])
# get the angle that the circle passes through the mid point
mid_angle = np.arctan2(pt_mid[1] - center[1], pt_mid[0] - center[0])
# convert the angles to degrees
start_angle = np.degrees(start_angle)
end_angle = np.degrees(end_angle)
mid_angle = np.degrees(mid_angle)
# convert all angles to be between 0 and 360
start_angle = start_angle % 360
end_angle = end_angle % 360
mid_angle = mid_angle % 360
# swap start with end if start is greater than end
if start_angle > end_angle:
start_angle, end_angle = end_angle, start_angle
print (f"start angle: {start_angle}, end angle: {end_angle}, mid angle: {mid_angle}")
travel_ang1 = end_angle - start_angle
travel_ang2 = - (360 - travel_ang1)
print (f"travel angle 1: {travel_ang1}, travel angle 2: {travel_ang2}")
# check if start < mid < end, if so, use travel_ang1
if start_angle < mid_angle < end_angle:
travel_angle = travel_ang1
else:
travel_angle = travel_ang2
center = (int(center[0]), int(center[1]))
radius = int(radius)
# draw the arc
# img = cv2.ellipse(img, (int(x), int(y)), (radius, radius), 0, np.degrees(start_angle), np.degrees(end_angle), color, line_width)
# start_angle = start_angle
# travel_angle = travel_ang2
img = cv2.ellipse(img, center, (radius, radius), 0, start_angle, start_angle + travel_angle, color, line_width)
# draw the center as a circle with radius 3
# img = cv2.circle(img, center, 3, color, line_width)
# draw the three control points as circles with radius 3
img = cv2.circle(img, pt_start, 3, color, line_width)
img = cv2.circle(img, pt_mid, 3, color, line_width)
img = cv2.circle(img, pt_end, 3, color, line_width)
return img
# make an exception called "colinear"
class Colinear(Exception):
pass
def get_color():
color = np.random.rand(3)
# check that it is above 0.5 for at least 1 channel
above_half = np.any(color > 0.5)
# check that the 3 channels are not close to each other (avoid gray)
diff = np.max(color) - np.min(color)
sufficient_diff = diff > 0.3
if above_half and sufficient_diff:
return color
else:
return get_color()
def transform(x, y):
# invert y first for drawing only
y = -y
'''
each point is [x,y] and both x and y range from -32 to 32
put it on the GRID 64x64
and draw it on IMG_W 320x320
'''
x = x + GRID
y = y + GRID
x = int(x * IMG_W / (GRID * 2))
y = int(y * IMG_W / (GRID * 2))
return x, y
def to_image(entry, draw_path, use_color=True):
# create a blank RBG image
img = np.zeros((IMG_W, IMG_W, 3))
# img = np.zeros((IMG_W, IMG_W))
entities = entry['entities']
for entity in entities:
# use a random color
if use_color:
# color is from 0 to 1
color = get_color()
else:
color = (1, 1, 1)
line_width = 4
# it is a line if it has 2 points
if len(entity) == 2:
(x1, y1), (x2, y2) = entity
x1, y1 = transform(x1, y1)
x2, y2 = transform(x2, y2)
# draw colored line with width 3
img = cv2.line(img, (x1, y1), (x2, y2), color, line_width)
# if 4 points it is a circle
elif len(entity) == 4:
# use only the 1st and 3rd point, that forms the diameter of the circle
pt1, pt2 = entity[0], entity[2]
pt1 = transform(pt1[0], pt1[1])
pt2 = transform(pt2[0], pt2[1])
# the center is between pt1 and pt2
center = ((pt1[0] + pt2[0])//2, (pt1[1] + pt2[1])//2)
# the radius is the distance between pt1 and pt2 divided by 2
radius = int(np.sqrt((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2) / 2)
img = cv2.circle(img, center, radius, color, line_width)
# if 3 points it is a 3 pt arc
elif len(entity) == 3:
try:
img = render_3pt_arc(entity, img, color, line_width)
except Colinear:
raise Colinear
else:
print ("oops")
print (entity)
assert 0, "something wrong"
# save the image as a png to ./drawings directory
if draw_path:
plt.imsave(draw_path, img)
# show the image
plt.imshow(img)
plt.show()
return img
if __name__ == '__main__':
from copy import deepcopy
# make a 3 point arc 2,0; sqrt2/2, sqrt2/2; 0,2
try_arc1 = [[0, 1], [np.sqrt(2)/2, np.sqrt(2)/2], [1, 0]]
try_arc2 = deepcopy(try_arc1)
# negate the mid point
try_arc2[1] = [-x for x in try_arc2[1]]
try_arc3 = [[-2, -2], [2, -2], [-2, 0]]
entry = {'entities': [[[-2, -2], [2, -2], [-2, 4]],
[[-2, -2], [0, 0], [-2, 5]],
[[-2, -2], [-1, -1], [-2, 6]],
[[-2, -2], [-3, 0], [-2, 7]],
[[-2, -2], [-4, 0], [-2, 8]],
[[-2, -2], [-5, 0], [-2, 9]]]}
# scale by 10 for better visualization
for entity in entry['entities']:
for i in range(len(entity)):
entity[i] = [3 * x for x in entity[i]]
try:
img = to_image(entry, './drawings/try_arc.png')
except Colinear:
print ("colinear arc, skipping...")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment