I had a need to answer this question as well, expanding on the chosen answer and criticism of it I created a method that converts to polar coordinates around the centroid, then check the radius within a given precision of the intersections of a target angle, and that angle minus 180deg. If at least one common radius exists between the two checks then that portion of the shape is considered symmetirc.
x,y = (np.array(v) for v in shape.exterior.coords.xy)
xc = shape.centroid.x
yc = shape.centroid.y
dx= x-xc
dy= y-yc
dth = np.arctan2(dx,dy)
rth = (dx**2 + dy**2)**0.5
inx = np.cumsum(np.ones(dth.size))-1
Nincr = 1000
Imax = inx.max()
inx2 = np.linspace(0,Imax,Nincr)
R = np.interp(inx2,inx,rth)
T = np.interp(inx2,inx,dth)
def find_radii(targetth,precision=3):
"""targetth must be positive from 0->pi"""
r = (dth - targetth)
#print(r)
if np.all(r < 0):
r = r + np.pi
elif np.all(r > 0):
r = r - np.pi
sgn = np.concatenate([ r[1:]*r[:-1], [r[0]*r[-1]] ] )
possibles = np.where( sgn < 0)[0]
#print(f'\n{targetth}')
out = set()
for poss in possibles:
poss2 = int((poss+1)%Imax)
x_ = np.array([r[poss],r[poss2]])
y_ = np.array([inx[poss],inx[poss2]])
itarget = (0 - x_[0])*(y_[1]-y_[0])/(x_[1]-x_[0]) + y_[0]
r_ = np.interp(itarget,inx2,R)
out.add(round(r_,precision))
#print(itarget,r_)
return out
syms = []
for targetth in np.linspace(0,np.pi,Nincr ):
o1 = find_radii(targetth)
o2 = find_radii(targetth-np.pi)
sym_point = len(o1.intersection(o2)) >= 1
syms.append(sym_point)
symmetric = np.all(syms)
print(f'symmetric: {symmetric}')