I could not find a fast and reliable concave hull and implemented my own (though a bit heterodox). It is fast and does the job (there is even an adjustment parameter) but it is a bit off. I got confuse with r,c vs x,y a couple of time maybe it has to do with that but I couldn't see it and any help is appreciated.
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
from skimage.measure import label, find_contours
import matplotlib.cm as cm
# points = datapoints
def concaveHull(points,pixel_adjustment=3):
mins = np.min(points, axis=0)
maxes = np.max(points, axis=0) - mins
#so if we make each point 10x10=100 pixels it should mostly fill the whole dummy image
points = points - mins
shape= np.array(10*maxes *np.sqrt(len(points))/ maxes.min(), dtype='int')+50
factor= shape.max() /points.max()
points_aligned = factor* (points - np.min(points,axis=0))
points_aligned = points_aligned.astype('int')
# plt.scatter(points_aligned[:,0],points_aligned[:,1])
# plt.show()
nmb_contours = 100
threshold = 3
hit=0
while ((nmb_contours > 1 ) or (hit<2)):
dummy_image = np.zeros(shape)
for i in points_aligned:
dummy_image[(i[0] - threshold):(i[0] + threshold), (i[1] - threshold):(i[1] + threshold)] = 1
dummy_image_larger = np.zeros(shape + 2)
dummy_image_larger[1:-1, 1:-1] = dummy_image
img_fill_holes = ndimage.morphology.binary_fill_holes(dummy_image_larger)
label_image = label(img_fill_holes)
contours = find_contours(label_image, 0.5)
nmb_contours = len(contours)
if len(contours) == 1:
contour = contours[0]
threshold += pixel_adjustment
if hit<1: print(f'threshold set to={threshold}')
hit += 1
else:
threshold += 1
continue
final_contour = mins+contour/factor
return final_contour
datapoints = np.random.rand(1000,2)
label_points = np.zeros((1000))
label_points[:] = 3
label_points[datapoints[:, 1] < 0.6]=2
label_points[datapoints[:, 1] < 0.3]=1
plt.scatter(datapoints[:,0],datapoints[:,1], c=label_points)
for subset_label in range(1,4):
subset = datapoints[label_points == subset_label]
outer_line=concaveHull(subset)
plt.fill(outer_line[:,0], outer_line[:,1], facecolor='none', edgecolor=cm.Set1(subset_label), lw=2)
plt.show()
label_points[:] = 3
label_points[datapoints[:, 1] < 0.6]=2
label_points[np.logical_and(datapoints[:, 1] < 0.9, datapoints[:, 0] < 0.15)]=1
label_points[np.logical_and(datapoints[:, 1] < 0.3, datapoints[:, 0] < 0.5)]=1
label_points[np.logical_and(datapoints[:, 1] < 0.3, datapoints[:, 0] > 0.7)]=4
fig, ax = plt.subplots()
ax.scatter(datapoints[:,0],datapoints[:,1], c=label_points)
for subset_label in range(1,5):
subset = datapoints[label_points == subset_label]
outer_line=concaveHull(subset)
plt.fill(outer_line[:,0], outer_line[:,1], facecolor='none',edgecolor=cm.Set1(subset_label), lw=2)
plt.show()