''' 2021-06-07 A One Shot The results (and possible code) posted elsewhere, I'm not going to bother with a link But I am sure it is in Code, somewhere Given the space remaining, And for a new image, that's all the space there is The largest possible circle is inserted Rinse Repeat Does not work perfectly. But close enough for hobby work. # # # Created on Aug 17, 2016 to 8-26-16 first go round reloaded 5-18-17 (at day 4-02 on webproject) @author: Brett Paufler Copyright Brett Paufler Intent was to make some sort of erosion island program As written makes filled circles ''' from drawing import Squares from Img import Img import numpy as np from skimage.draw import circle as circle_indexes from scipy.ndimage.morphology import binary_erosion from scipy.spatial.distance import cdist #, pdist from sklearn.preprocessing import normalize class CircleFill(Squares, Img): @staticmethod def stretch(img): img -= np.min(img) img /= np.max(img) return img @staticmethod def norm(img): '''Similar to stretch, but has distortion. This was used for the nice eye effect.''' img = normalize(img, axis=0, norm='max') return img @staticmethod def grey_to_color(img, color=(255,255,255)): '''Converts one layer grayscale to color img.''' img = np.dstack((img, img, img)) img = img * color img = np.array(img, dtype=np.uint8) return img def __init__(self, **kwargs): '''Takes standard Square or Img kwargs, like size.''' super(CircleFill, self).__init__(**kwargs) def gradient(self, x=0, y=0, size=None): '''Returns a grayscale gradient Fast enough, can likely use full size all the time So, size option may not be required x: x cordinate (left, right) of center y: y cordiante (top, bottome) of center ''' size = size if size else self.size x_axis = np.linspace( start=(-x), stop=(size - x), num=size, dtype=np.float64) y_axis = np.linspace( start=(-y), stop=(size - y), num=size, dtype=np.float64) img = [] for y in y_axis: img.append(np.hypot(x_axis, y)) img = np.vstack(img) img = self.stretch(img) #For 'eye' use self.norm(img) return img def circle_mask(self, x, y, radius=1000): '''Returns greyscale img of circle (to be used as a mask) 1.0 greyscale at circle location 0.0 elsewhere x, y: coordinates from upper left radius: of circle (as apposed to diameter)''' mask = self.new_greyscale_img() rr, cc = circle_indexes( r=y, c=x, radius=radius, shape=self.img.shape) mask[rr, cc] = 1.0 return mask def circle(self, x, y, radius, color, background='solid'): '''Adds circle to self.img (colors are additive) x, y: coordinates from upper left color: RBG color of circle background (centered on center of circle): solid: constant throughout edge: 0 -> full color, brightest at edge center: full color -> 0, brightest at center ''' if background == 'solid': background_img = self.new_greyscale_img(value=1.0) elif background == 'edge': background_img = self.gradient(x, y) elif background == 'center': background_img = 1 - self.gradient(x, y) img = np.where( i.circle_mask(x, y, radius), background_img, 0) img = self.stretch(img) img = self.grey_to_color(img, color) self.img = np.where( img, img, self.img) def find_biggest_hole_center(self): '''Returns (x,y) center index for largest hole Note: there is distortion at edge Hole is any non-color area: Black == (0, 0, 0) Colored == (1, 0, 0) or greater''' #Mask with 0 at any color, 1 otherwise = True at blank holes = temp = np.where(self.img.any(-1), 0, 1) test = np.sum(temp) radius = 0 while test: holes = temp temp = binary_erosion(holes).astype(holes.dtype) test = np.sum(temp) radius += 1 x = np.nonzero(holes)[1][0] #columns y = np.nonzero(holes)[0][0] #rows return x, y, radius @staticmethod def distance_to_anything(radius, data_array): center = np.array([[radius, radius]]) objects = np.nonzero(data_array) if len(objects[0]) == 0: return data_array.shape[0] / 2 objects = np.array(zip(objects[1], objects[0])) print objects radiuses = cdist(center, objects, 'euclidean') print radiuses radius = radiuses.min() #prind if radius > data_array.shape[0] / 2: radius = data_array.shape[0] / 2 print 'Distance: ', radius return int(radius) def fill_with_circles(self, min_radius=5, background='solid', max_circles=None, color=(225,255,50)): '''Fills self.img with circles (fractal ish) placing largest possible circles first min_radius: smallest circle to place background: gradient option solid, edge, center max_circles: if want to end before min_radius indicate max number of circles to place color: any rgb color''' loops = 0 radius = self.img.shape[0] while radius > min_radius: x, y, radius = self.find_biggest_hole_center() radius -= 1 #correcting so not out of bounds square = self.img[y-radius:y+radius, x-radius:x+radius] rad_check = self.distance_to_anything(radius, square) radius = min([radius, rad_check]) self.circle(x, y, radius, color=color, background=background) print "New Circle: ", x, y, radius, color if max_circles: loops += 1 if loops == max_circles: break if __name__ == '__main__': print 'START: CircleFill' i = CircleFill(size=99) print 'Size: ', i.size i.tag_name('07_01_new_day') #i.circle(500, 500, 500, color=(0,255,0), background='solid') #i.grid(color=(255,0,0), num_verticals=9, num_horizontals=9) #i.crosshatch(lines=5, color=(255,0,0)) i.fill_with_circles( min_radius=2, background='edge', max_circles=1000) i.save() print 'FINISH: CircleFill'