diff --git a/InverseTransformSampling.py b/InverseTransformSampling.py index a9757aa..f23b906 100644 --- a/InverseTransformSampling.py +++ b/InverseTransformSampling.py @@ -1,36 +1,43 @@ import numpy as np -def pdftocdf(x,y,p,shape): - xs,ys = (shape[0]/np.max(x.flatten()),shape[1]/np.max(y.flatten())) - px = np.sum(p,axis=1) + +def pdftocdf(x, y, p, shape): + xs, ys = (shape[0] / np.max(x), shape[1] / np.max(y)) # correction for arange not always reaching the end value + px = np.sum(p, axis=1) # px is marginal CDF for x alone px = np.cumsum(px) - p = np.apply_along_axis(np.cumsum,0,p) - p = np.apply_along_axis(np.cumsum,1,p) - p = p/np.max(p.flatten()) - return (p,x*xs,y*ys,px/np.max(px.flatten())) - -def samplecdf1(ux,uy,p,x,y,px): # no interpolation - a1 = np.argmin(np.abs(px-ux)) - sel_x = x[0,:][a1] - sel_y = y[:,0][np.argmin(np.abs(p[a1,:]/np.max(p[a1,:].flatten())-uy))] - return sel_x,sel_y - -def transformp(cn,p,x,y,shape): - p,x,y,px = pdftocdf(x,y,p,shape) + px = px / np.max(px) + p = np.apply_along_axis(np.cumsum, 0, p) # p is the joint CDF for x,y + p = np.apply_along_axis(np.cumsum, 1, p) + p = p / np.max(p) + return (p, x * xs, y * ys, px) + + +def samplecdf1(ux, uy, p, x, y, px): # no interpolation - density of the p grid determines the smoothness of the output + a1 = np.argmin(np.abs(px - ux)) # ux,uy are the uniform random variables for Inverse Transformation Sampling + sel_x = x[0, :][a1] # sample x based on ux first + sel_y = y[:, 0][np.argmin(np.abs(p[a1, :] / np.max(p[a1, :]) - uy))] # p[a1,:] give the marginal CDF for y at sel_x + return sel_x, sel_y + + +def transformp(cn, p, x, y, shape): + p, x, y, px = pdftocdf(x, y, p, shape) ans = [] for i in range(cn): - ans.append(samplecdf1(np.random.rand(),np.random.rand(),p,x,y,px)) + ans.append(samplecdf1(np.random.rand(), np.random.rand(), p, x, y, px)) return ans -def gaussian(mx,my,sigmax,sigmay,corr=0,spacing=None,shape=None): - mx = mx*shape[0] - my = my*shape[1] + +def gaussian(mx, my, sigmax, sigmay, corr=0, spacing=None, shape=None): + mx = mx * shape[0] # Find the centres (mx,my are relative distances) + my = my * shape[1] if spacing is None: - x = np.linspace(0,shape[0],1500) - y = np.linspace(0,shape[1],1500) + x = np.linspace(0, shape[0], 1500) + y = np.linspace(0, shape[1], 1500) else: - x = np.arange(0,shape[0],spacing) - y = np.arange(0,shape[1],spacing) - x,y = np.meshgrid(x,y) - p = 1/(2*np.pi*sigmax*sigmay*np.sqrt(1-corr**2))*np.exp(-1/(2-2*corr**2)*((x-mx)**2/sigmax**2 + (y-my)**2/sigmay**2 - 2*corr*(x-mx)*(y-my)/sigmax/sigmay)) - return (p,x,y) \ No newline at end of file + x = np.arange(0, shape[0], spacing) + y = np.arange(0, shape[1], spacing) + x, y = np.meshgrid(x, y) + p = 1 / (2 * np.pi * sigmax * sigmay * np.sqrt(1 - corr ** 2)) * np.exp(-1 / (2 - 2 * corr ** 2) * ( + (x - mx) ** 2 / sigmax ** 2 + (y - my) ** 2 / sigmay ** 2 - 2 * corr * (x - mx) * ( + y - my) / sigmax / sigmay)) # The multivariate Gaussian distribution - returns a probmap + return (p, x, y) diff --git a/README.md b/README.md index c82166a..326c16c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # VoronoiTessellations -Python3 script to create [Voronoi](https://en.wikipedia.org/wiki/Voronoi_diagram) Tessellations (Mosaic pattern) on images. The script basically does 2D k-means-like clustering of the pixels based on fixed pre-defined cluster centres, and averages the RGB values for each cluster group and assigns all the pixels in the group the average value. Further options allow selectively applying average to only one of the RGB channels or exchanging values of the channels randomly (see [docs](https://github.com/Stochastic13/VoronoiTessellations/blob/master/VorTes%20docs.pdf)). +Python3 script to create [Voronoi](https://en.wikipedia.org/wiki/Voronoi_diagram) Tessellations (Mosaic pattern) on images. The script basically does 2D k-means-like clustering of the pixels based on fixed pre-defined cluster centres (which can be set to be random), and averages the RGB values for each cluster group and assigns all the pixels in the group the average value. Further options allow selectively applying average to only one of the RGB channels or exchanging values of the channels randomly (see [docs](https://github.com/Stochastic13/VoronoiTessellations/blob/master/VorTes%20docs.pdf)). This is my first ever open-source repository. :) @@ -29,7 +29,7 @@ You can run the following command to see help: positional arguments: input Input Image file output Output Image file - cn Number of clusters (default = 0.1*size) + cn Number of clusters (default = 0.1*smaller_dimension) optional arguments: -h, --help show this help message and exit @@ -62,11 +62,15 @@ Source:
+ gaussian probmap using the `--gaussian` option with `--gaussianvars 0.3 0.8 90 150` +
+ `--channel rand` and `--channel randdual` +
diff --git a/VorTes docs.pdf b/VorTes docs.pdf index 53ef27b..b70eff6 100644 Binary files a/VorTes docs.pdf and b/VorTes docs.pdf differ diff --git a/VoronoiMain.py b/VoronoiMain.py index e62c523..4417d1a 100644 --- a/VoronoiMain.py +++ b/VoronoiMain.py @@ -2,118 +2,126 @@ import numpy as np from tessellate_fast import tessel_fast from tessellate_lowmem import tessel_low_mem -from InverseTransformSampling import transformp,gaussian +from InverseTransformSampling import transformp, gaussian import argparse # Parse command line arguments parser = argparse.ArgumentParser() -parser.add_argument('input',help='Input Image file') -parser.add_argument('output',help='Output Image file') -parser.add_argument('cn',default=0,help='Number of clusters (default = 0.1*size)',type=int) -parser.add_argument('--rescale',default = 1,help='Rescaling factor for large images',type=float) -parser.add_argument('--border',default=0,help='Make border [1/0]?',type=int) -parser.add_argument('--method',default='low_mem',help='fast vs low_mem methods. Default is low_mem.') -parser.add_argument('--threshold',default=200,help='Only for borders. Threshold distance.',type=float) -parser.add_argument('--clusmap',default=0,help='Load a specific cluster map as tab-separated text file') -parser.add_argument('--probmap',default=0,help='Load a 2D probability map for cluster generation') -parser.add_argument('--channel',default=0,help='Whether to tessellate along only R,G,B or combinations?',choices=['r','g','b','rand','rb','rg','bg','randdual']) -parser.add_argument('--verbose',default=1,help='Print progress?[1/0]',type=int) -parser.add_argument('--seed',default='None',help='Seed for PRNG') -parser.add_argument('--gaussianvars',nargs='*',help='Only for gaussian probmap (mx,my,sigmax,sigmay,corr(opt),spacing(opt))') +parser.add_argument('input', help='Input Image file') +parser.add_argument('output', help='Output Image file') +parser.add_argument('cn', default=0, help='Number of clusters (default = 0.1*smaller_dimension)', type=int) +parser.add_argument('--rescale', default=1, help='Rescaling factor for large images', type=float) +parser.add_argument('--border', default=0, help='Make border [1/0]?', type=int) +parser.add_argument('--method', default='low_mem', help='fast vs low_mem methods. Default is low_mem.') +parser.add_argument('--threshold', default=200, help='Only for borders. Threshold distance.', type=float) +parser.add_argument('--clusmap', default=0, help='Load a specific cluster map as tab-separated text file') +parser.add_argument('--probmap', default=0, help='Load a 2D probability map for cluster generation') +parser.add_argument('--channel', default=0, help='Whether to tessellate along only R,G,B or combinations?', + choices=['r', 'g', 'b', 'rand', 'rb', 'rg', 'bg', 'randdual']) +parser.add_argument('--verbose', default=1, help='Print progress?[1/0]', type=int) +parser.add_argument('--seed', default='None', help='Seed for PRNG') +parser.add_argument('--gaussianvars', nargs='*', + help='Only for gaussian probmap (mx,my,sigmax,sigmay,corr(opt),spacing(opt))') args = parser.parse_args() # seed -if not args.seed=='None': +if not args.seed == 'None': np.random.seed(int(args.seed)) # load and rescale input image img = Image.open(args.input) -img = img.resize((int(img.size[0]/args.rescale),int(img.size[1]/args.rescale))) +img = img.resize((int(img.size[0] / args.rescale), int(img.size[1] / args.rescale))) img = np.array(img) # verbose mode? -verb = [False,True][args.verbose] - +verb = [False, True][args.verbose] if verb: - print('Making Clusters.') + print('Making cluster centers.') + +# default cn +if args.cn == 0: + args.cn = int(0.1 * np.min(img.shape)) + # Cluster generation -if not (args.clusmap == 0): # Pre-formed cluster-map +if not (args.clusmap == 0): # Pre-formed cluster-map is desired clusters = [] with open(args.clusmap) as f: for row in f: - row = row.split('\n')[0] - clusters.append(list(map(float,row.split('\t')))) + row = row.split('\n')[0] # Just for extra protection against bad lines? + clusters.append(list(map(float, row.split('\t')))) clusters = np.array(clusters) args.cn = clusters.shape[0] -elif not (args.probmap == 0): #Probability distribution for Inverse Transform Sampling +elif not (args.probmap == 0): # Probability distribution for Inverse Transform Sampling given if args.probmap == 'gaussian': - if len(args.gaussianvars)<6: - defs = [0.5,0.5,100,100,0,None] - args.gaussianvars = list(map(float,args.gaussianvars)) + defs[len(args.gaussianvars):len(defs)] - arguments = args.gaussianvars+[img.shape] + if len(args.gaussianvars) < 6: + defs = [0.5, 0.5, 100, 100, 0, None] # defaults + args.gaussianvars = list(map(float, args.gaussianvars)) + defs[len(args.gaussianvars):len(defs)] + arguments = args.gaussianvars + [img.shape] g = gaussian(*arguments) - clusters = transformp(args.cn,g[0],g[1],g[2],img.shape) + clusters = transformp(args.cn, g[0], g[1], g[2], img.shape) clusters = np.array(tuple(clusters)) -else: - clusters = np.array(tuple(zip(np.random.rand(args.cn)*img.shape[0],np.random.rand(args.cn)*img.shape[1]))) +else: # random cluster map + clusters = np.array(tuple(zip(np.random.rand(args.cn) * img.shape[0], np.random.rand(args.cn) * img.shape[1]))) if verb: - print('Done.') + print('Cluster centers are ready.') print('Making Voronoi Tessellations....') # Tessellating -if args.method=='fast': - dist = tessel_fast(clusters,img.shape,[False,True][args.verbose],[False,True][args.border],args.threshold) -elif args.method=='low_mem': - dist = tessel_low_mem(clusters,img.shape,[False,True][args.verbose],[False,True][args.border],args.threshold) +if args.method == 'fast': # dist is the cluster membership array + dist = tessel_fast(clusters, img.shape, [False, True][args.verbose], [False, True][args.border], args.threshold) +elif args.method == 'low_mem': + dist = tessel_low_mem(clusters, img.shape, [False, True][args.verbose], [False, True][args.border], args.threshold) else: print("ERROR: Invalid Method") - quit(1) + quit(5) if verb: print('\t\t\t\t\t\nDone.') -# Averaging over Voronoi clusters +# Averaging pixels over the clusters s = set(dist.flatten()) sl = len(s) -x=0 # counter for Verbose mode +x = 0 # counter for verbose mode if verb: print('\nAveraging over Voronoi clusters.') -for i in (set(list(range(args.cn))) & s): +for i in (set(list(range(args.cn))) & s): # To exclude centers without any membership if verb: - print(str(int(x/sl*100))+'% done \t\t\r',end='') + print(str(int(x / sl * 100)) + '% done \t\t\r', end='') x += 1 - if args.channel in ['r','g','b']: - chn = {'r':0,'g':1,'b':2}[args.channel] + if args.channel in ['r', 'g', 'b']: # If averaging only along 1 channel + chn = {'r': 0, 'g': 1, 'b': 2}[args.channel] img[dist == i, chn] = int(np.mean(img[dist == i, chn].flatten())) continue - elif args.channel=='rand': - chn = np.random.randint(0,3) + elif args.channel == 'rand': # Randomly select a channel and average + chn = np.random.randint(0, 3) img[dist == i, chn] = int(np.mean(img[dist == i, chn].flatten())) continue - elif args.channel=='randdual': - chn1 = np.random.randint(0,3) - chn2 = np.random.randint(0,3) - img[dist == i, chn1],img[dist == i, chn2] = (np.array(img[dist == i, chn2],copy=True),np.array(img[dist == i, chn1],copy=True)) + elif args.channel == 'randdual': # Randomly exchange the channels + chn1 = np.random.randint(0, 3) + chn2 = np.random.randint(0, 3) + img[dist == i, chn1], img[dist == i, chn2] = ( + np.array(img[dist == i, chn2], copy=True), np.array(img[dist == i, chn1], copy=True)) continue - elif args.channel in ['rb','rg','gb']: - perms = ['rb','rg','gb'] - chn1,chn2 = [(0,2),(0,1),(1,2)][perms.index(args.channel)] - chn1,chn2 = [(chn1,chn2),(0,0)][np.random.randint(0,2)] - if chn1==chn2: + elif args.channel in ['rb', 'rg', 'gb']: # Exchange two channels (specific) + perms = ['rb', 'rg', 'gb'] + chn1, chn2 = [(0, 2), (0, 1), (1, 2)][perms.index(args.channel)] + chn1, chn2 = [(chn1, chn2), (0, 0)][np.random.randint(0, 2)] + if chn1 == chn2: continue - img[dist == i, chn1],img[dist == i, chn2] = (np.array(img[dist == i, chn2],copy=True),np.array(img[dist == i, chn1],copy=True)) + img[dist == i, chn1], img[dist == i, chn2] = ( + np.array(img[dist == i, chn2], copy=True), np.array(img[dist == i, chn1], copy=True)) continue - img[dist==i,0]=int(np.mean(img[dist==i,0].flatten())) - img[dist==i,1]=int(np.mean(img[dist==i,1].flatten())) - img[dist==i,2]=int(np.mean(img[dist==i,2].flatten())) -if [False,True][args.border]: - img[dist==args.cn+1,0]=0 - img[dist==args.cn+1,1]=0 - img[dist==args.cn+1,2]=0 + img[dist == i, 0] = int(np.mean(img[dist == i, 0])) # The vanilla averaging + img[dist == i, 1] = int(np.mean(img[dist == i, 1])) + img[dist == i, 2] = int(np.mean(img[dist == i, 2])) +if [False, True][args.border]: + img[dist == args.cn + 1, 0] = 0 # If drawing borders is set to True + img[dist == args.cn + 1, 1] = 0 + img[dist == args.cn + 1, 2] = 0 if verb: print('\t\t\t\t\t\nDone.') - print('Saving Output file.') + print('Saving output file.') img = Image.fromarray(img) img.save(args.output) diff --git a/gui_clusmap.py b/gui_clusmap.py index be7caf1..6e77392 100644 --- a/gui_clusmap.py +++ b/gui_clusmap.py @@ -4,26 +4,28 @@ import numpy as np import sys + def labelupdate(which): - if which==0: + if which == 0: stat.set('Dot Mode') - if which==1: + if which == 1: stat.set('Gaussian Spray') + def saver(event=None): global finalarr - finalarr = finalarr*fac - finalarr[:,0],finalarr[:,1]=(np.array(finalarr[:,1],copy=False),np.array(finalarr[:,0],copy=True)) - with open(outfilename,'w',newline='') as f: - f.write('\n'.join(list(map(lambda x: '\t'.join([str(x[0]),str(x[1])]),finalarr)))) + finalarr = finalarr * fac + finalarr[:, 0], finalarr[:, 1] = (np.array(finalarr[:, 1], copy=True), np.array(finalarr[:, 0], copy=True)) + with open(outfilename, 'w', newline='') as f: + f.write('\n'.join(list(map(lambda x: '\t'.join([str(x[0]), str(x[1])]), finalarr)))) root.destroy() def uniform(event=None): global finalarr n = int(scale2.get()) - px = np.random.rand(n)*img.size[0] - py = np.random.rand(n)*img.size[1] + px = np.random.rand(n) * img.size[0] + py = np.random.rand(n) * img.size[1] if len(finalarr) == 0: finalarr = np.array(tuple(zip(px.tolist(), py.tolist()))) else: @@ -32,43 +34,45 @@ def uniform(event=None): def move_sprayer(event): - if stat.get()=='Gaussian Spray': + if stat.get() == 'Gaussian Spray': mainc.delete('pointer') sd = int(scale.get()) - mainc.create_oval((event.x-sd,event.y-sd,event.x+sd,event.y+sd),fill='',tags=('pointer',)) + mainc.create_oval((event.x - sd, event.y - sd, event.x + sd, event.y + sd), fill='', tags=('pointer',)) + def draw_points(): mainc.delete('points') - if len(finalarr.shape)>1: + if len(finalarr.shape) > 1: for i in finalarr: - valx,valy = i - cx,cy = (int(valx),int(valy)) - mainc.create_oval((cx-1,cy-1,cx+1,cy+1),fill='red',outline='red',tag='points') + valx, valy = i + cx, cy = (int(valx), int(valy)) + mainc.create_oval((cx - 1, cy - 1, cx + 1, cy + 1), fill='red', outline='red', tag='points') else: - valx,valy = finalarr - cx, cy = (int(valx),int(valy)) + valx, valy = finalarr + cx, cy = (int(valx), int(valy)) mainc.create_oval((cx - 1, cy - 1, cx + 1, cy + 1), fill='red', outline='red', tag='points') + def spray(event): global finalarr - mode = {'Gaussian Spray':1,'Dot Mode':0}[stat.get()] - if mode==0: + mode = {'Gaussian Spray': 1, 'Dot Mode': 0}[stat.get()] + if mode == 0: px = mainc.canvasx(event.x) py = mainc.canvasy(event.y) - if len(finalarr)==0: - finalarr = np.array([float(px),float(py)]) + if len(finalarr) == 0: + finalarr = np.array([float(px), float(py)]) else: - finalarr = np.vstack((finalarr,np.array([float(px),float(py)]))) + finalarr = np.vstack((finalarr, np.array([float(px), float(py)]))) draw_points() - elif mode==1: + elif mode == 1: px = mainc.canvasx(event.x) py = mainc.canvasy(event.y) - px = np.random.normal(float(px),float(scale.get()),int(nums.get())) - py = np.random.normal(float(py),float(scale.get()),int(nums.get())) - if len(finalarr)==0: - finalarr = np.array(tuple(zip(px.tolist(),py.tolist()))) + px = np.random.normal(float(px), float(scale.get()), int(nums.get())) + py = np.random.normal(float(py), float(scale.get()), int(nums.get())) + if len(finalarr) == 0: + finalarr = np.array(tuple(zip(px.tolist(), py.tolist()))) else: - finalarr = np.vstack((finalarr,np.array(tuple(zip(px.tolist(),py.tolist()))))) + finalarr = np.vstack((finalarr, np.array(tuple(zip(px.tolist(), py.tolist()))))) draw_points() @@ -78,47 +82,48 @@ def reset(event=None): mainc.delete('points') -finalarr = np.array([]) +finalarr = np.array([]) # To be converted into output filename = sys.argv[1] outfilename = sys.argv[2] -root = tk.Tk() +root = tk.Tk() # Root window img = Image.open(filename) -fac = 1 -if img.size[0]>1200 or img.size[1]>900: - fac = max((img.size[0]/1000),(img.size[1]/700)) -img = img.resize((int(img.size[0]/fac),int(img.size[1]/fac))) -mainf = tk.Frame(root,width=img.size[0]+200,height=img.size[1]) +fac = 1 # To adjust display size +if img.size[0] > 1200 or img.size[1] > 900: + fac = max((img.size[0] / 1000), (img.size[1] / 700)) +img = img.resize((int(img.size[0] / fac), int(img.size[1] / fac))) +mainf = tk.Frame(root, width=img.size[0] + 200, height=img.size[1]) # Main frame mainf.pack() -mainc = tk.Canvas(mainf,width=img.size[0],height=img.size[1],bg='white') -mainc.grid(row=0,column=0,sticky='ns') -panel = tk.Frame(mainf,width=200,height=img.size[1],bg='white') -panel.grid(row=0,column=1,sticky='ns') -sharp = ImageEnhance.Sharpness(img) +mainc = tk.Canvas(mainf, width=img.size[0], height=img.size[1], bg='white') # Canvas for displaying image +mainc.grid(row=0, column=0, sticky='ns') +panel = tk.Frame(mainf, width=200, height=img.size[1], bg='white') +panel.grid(row=0, column=1, sticky='ns') +sharp = ImageEnhance.Sharpness(img) # To allow for better visualisation despite increasing alpha img = sharp.enhance(0) img.putalpha(210) im = PhotoImage(img) -imc = mainc.create_image(0,0,image=im,anchor=tk.NW) -mainc.image=im -mainc.bind('',move_sprayer) -mainc.bind('',spray) +imc = mainc.create_image(0, 0, image=im, anchor=tk.NW) +mainc.image = im # Reference to not let the image be garbage collected +mainc.bind('', move_sprayer) +mainc.bind('', spray) stat = tk.StringVar(root) stat.set('Select a mode') -l0 = tk.Label(panel,textvariable=stat,bg='black',fg='white',font=('lucida console',20)) -l1 = tk.Button(panel,text='Dot Mode',command = lambda : labelupdate(0)) -l2 = tk.Button(panel,text='Gaussian Spray mode',command = lambda : labelupdate(1)) -nums = tk.Scale(panel,label='No. per click',from_=1,to=500,orient=tk.HORIZONTAL,resolution=1) -l0.grid(row=0,column=0,sticky='ew') -l1.grid(row=1,column=0,sticky='ew') -l2.grid(row=2,column=0,sticky='ew',pady=(20,0)) -scale = tk.Scale(panel,from_=int(0.01*min(img.size)),to=max(img.size),label='SD for Spray',orient=tk.HORIZONTAL,resolution=0.1) -nums.grid(row=3,column=0,sticky='ew') -scale.grid(row=4,column=0,sticky='ew') -l3 = tk.Button(panel,text='add Uniform Random',command=uniform) -l3.grid(row=5,column=0,sticky='ew',pady=(20,0)) -scale2 = tk.Scale(panel,from_=1,to=5000,label='No. of points to add',orient=tk.HORIZONTAL) -scale2.grid(row=6,column=0,sticky='ew') -l4 = tk.Button(panel,text='Reset',command=reset) -l4.grid(row=7,column=0,sticky='ew',pady=10) -l5 = tk.Button(panel,text='Save',command=saver) -l5.grid(row=8,column=0,sticky='ew') +l0 = tk.Label(panel, textvariable=stat, bg='black', fg='white', font=('lucida console', 20)) +l1 = tk.Button(panel, text='Dot Mode', command=lambda: labelupdate(0)) +l2 = tk.Button(panel, text='Gaussian Spray mode', command=lambda: labelupdate(1)) +nums = tk.Scale(panel, label='No. per click', from_=1, to=500, orient=tk.HORIZONTAL, resolution=1) +l0.grid(row=0, column=0, sticky='ew') +l1.grid(row=1, column=0, sticky='ew') +l2.grid(row=2, column=0, sticky='ew', pady=(20, 0)) +scale = tk.Scale(panel, from_=int(0.01 * min(img.size)), to=max(img.size), label='SD for Spray', orient=tk.HORIZONTAL, + resolution=0.1) +nums.grid(row=3, column=0, sticky='ew') +scale.grid(row=4, column=0, sticky='ew') +l3 = tk.Button(panel, text='add Uniform Random', command=uniform) +l3.grid(row=5, column=0, sticky='ew', pady=(20, 0)) +scale2 = tk.Scale(panel, from_=1, to=5000, label='No. of points to add', orient=tk.HORIZONTAL) +scale2.grid(row=6, column=0, sticky='ew') +l4 = tk.Button(panel, text='Reset', command=reset) +l4.grid(row=7, column=0, sticky='ew', pady=10) +l5 = tk.Button(panel, text='Save', command=saver) +l5.grid(row=8, column=0, sticky='ew') root.mainloop() diff --git a/tessellate_fast.py b/tessellate_fast.py index 3606461..4770d33 100644 --- a/tessellate_fast.py +++ b/tessellate_fast.py @@ -1,23 +1,24 @@ import numpy as np -def bordercalc(x,threshold,cn): - if True in (-np.diff(np.sort(x,0)))<=threshold: - return cn+1 - else: - return np.argmin(x) +def bordercalc(x, threshold, cn): + if True in (-np.diff(np.sort(x, 0))) <= threshold: + return cn + 1 + else: + return np.argmin(x) -def tessel_fast(clusters,shape,verbose=True,border=False,threshold=200): + +def tessel_fast(clusters, shape, verbose=True, border=False, threshold=200): if verbose: - print("Loading Array of Indices") - indices = np.indices((shape[0],shape[1]),dtype=np.int16).transpose([1,2,0]) + print("Loading array of indices") + indices = np.indices((shape[0], shape[1]), dtype=np.uint16).transpose([1, 2, 0]) if verbose: - print('Computing Distances.') - indices = np.sum((clusters.transpose()[None,None,:,:]-indices[:,:,:,None])**2,axis=2) - if not border: + print('Computing distances.') + indices = np.sum((clusters.transpose()[None, None, :, :] - indices[:, :, :, None]) ** 2, axis=2) + if not border: # I seem to have done some indexing dark magic above. It is all Greek to me now. if verbose: print('Assigning Clusters.') - return np.apply_along_axis(np.argmin,2,indices) + return np.apply_along_axis(np.argmin, 2, indices) if verbose: print('Assigning Clusters and Border tags.') - return np.apply_along_axis(lambda x: bordercalc(x,threshold,len(clusters)),2,indices) \ No newline at end of file + return np.apply_along_axis(lambda x: bordercalc(x, threshold, len(clusters)), 2, indices) diff --git a/tessellate_lowmem.py b/tessellate_lowmem.py index 1e7a4bd..af75e49 100644 --- a/tessellate_lowmem.py +++ b/tessellate_lowmem.py @@ -1,28 +1,31 @@ import numpy as np -def bordercalc(a,t,amin): - m1 = a[amin] + +def bordercalc(a, t, amin): # If drawing borders is True + m1 = a[amin] # Distance from the closest cluster center a = a.tolist() - m2 = np.min(a[0:amin]+a[amin+1:len(a)]) + m2 = np.min(a[0:amin] + a[amin + 1:len(a)]) # Distance from the second closest cluster center. if m2 - m1 <= t: return True else: return False -def tessel_low_mem(clusters,shape,verbose=True,border=False,threshold=200): - dist = np.zeros(shape[0:2], dtype=np.int16) - for i in range(1,shape[0]+1): +def tessel_low_mem(clusters, shape, verbose=True, border=False, threshold=200): + dist = np.zeros(shape[0:2], dtype=np.uint16) # The 2D cluster membership array (up to 65536 clusters) + for i in range(1, shape[0] + 1): if verbose: print(str(int(i / shape[0] * 100)) + '% done \t\t\r', end='') - for j in range(1,shape[1] + 1): + for j in range(1, shape[1] + 1): subdists = (clusters[:, 0] - i) ** 2 + (clusters[:, 1] - j) ** 2 clus = np.argmin(np.array(subdists)) - if border: + if border: # I am assuming the np.array() call above is just for my mental peace and serves no purpose if bordercalc(subdists, threshold, clus): - dist[i - 1, j - 1] = len(clusters[:,0]) + 1 + dist[i - 1, j - 1] = len(clusters[:, 0]) + 1 # Special index to identify borders else: dist[i - 1, j - 1] = clus else: - dist[i - 1, j - 1] = clus - return dist \ No newline at end of file + dist[ + i - 1, j - 1] = clus # I can't see why I start i,j from 1, and then subtract 1 at the end. + # But I am keeping it like this in hope that my past self had some rationale. + return dist