forked from ebenolson/kaggle-ndsb-visualization
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmake_bubblechart.py
173 lines (147 loc) · 5.35 KB
/
make_bubblechart.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import os, sys
import pickle
from PIL import Image
from numpy import array, tile, sqrt, ceil, floor, zeros, arange
from matplotlib.pyplot import imread
import circlepack
#############################################################
## Calculate bubble hierachy and positions from taxonomy file
#############################################################
f = open("taxonomy.txt", "r")
depth = 0
root = { "name": "root", "children": [] }
parents = []
node = root
for line in f:
line = line.rstrip()
newDepth = len(line) - len(line.lstrip("\t")) + 1
# if the new depth is shallower than previous, we need to remove items from the list
if newDepth < depth:
parents = parents[:newDepth]
# if the new depth is deeper, we need to add our previous node
elif newDepth == depth + 1:
parents.append(node)
# levels skipped, not possible
elif newDepth > depth + 1:
raise Exception("Invalid file")
depth = newDepth
# create the new node
node = {'name': line.strip().replace(' ', '_'), 'depth': depth, 'children':[]}
# add the new node into its parent's children
parents[-1]["children"].append(node)
def calc_bubbles(d):
if 'children' in d:
if d['children'] == []:
d.pop('children')
h, w, _ = imread('./mosaics/{0}.png'.format(d['name'])).shape
d['radius'] = sqrt(h**2/4 + w**2/4)
print 'Bubbled {0}'.format(d["name"])
return d["name"]
else:
for child in d["children"]:
calc_bubbles(child)
rs = [child['radius'] for child in d['children']]
ys, xs, rb = circlepack.pack_circles(rs)
d['radius'] = rb
for y, x, child in zip(ys, xs, d['children']):
child['x'] = x
child['y'] = y
calc_bubbles(root)
root['x'] = 0
root['y'] = 0
root['depth'] = 0
pickle.dump(root, open('bubbles.pickle','w'))
###################################
## Render bubbles and mosaic images
###################################
TILEW = 1024
NTILES = int(ceil(root['radius']*2/TILEW))
out = zeros((NTILES*TILEW, NTILES*TILEW, 3), dtype='uint8')
out[:,:,:] = 255
fullx = arange(out.shape[0])
fullx = fullx-fullx.mean()
fully = fullx
def render_circle(x0, y0, r0, color):
r02 = r0**2
for i in range(NTILES):
for j in range(NTILES):
tileRmin = sqrt((fullx[i*TILEW]-x0)**2 + (fully[j*TILEW]-y0)**2) - sqrt(TILEW**2 * 2)
tileRmax = sqrt((fullx[i*TILEW]-x0)**2 + (fully[j*TILEW]-y0)**2) + sqrt(TILEW**2 * 2)
if tileRmin > r0:
continue
if tileRmax < r0:
out[j*TILEW:(j+1)*TILEW, i*TILEW:(i+1)*TILEW] = color
continue
tilex = fullx[i*TILEW:(i+1)*TILEW]
tiley = fully[j*TILEW:(j+1)*TILEW]
r2 = (tile(tilex,(1024,1))-x0)**2 + (tile(tiley,(1024,1))-y0).T**2
out[j*TILEW:(j+1)*TILEW, i*TILEW:(i+1)*TILEW][r2<=r02] = color
def render_bubbles(d, x=0, y=0):
if 'name' in d:
print 'Drawing bubble for {0}'.format(d['name'])
sys.stdout.flush()
x = x+d['x']
y = y+d['y']
r = d['radius']
c = array([0,d['depth']*0.1+0.2,d['depth']*0.1+0.3])*255
render_circle(x, y, r, c)
if 'children' in d:
for child in d['children']:
render_bubbles(child,x,y)
def render_image(d, x=0, y=0):
x = x+d['x']
y = y+d['y']
r = d['radius']
if 'children' in d:
for child in d['children']:
render_image(child,x,y)
else:
print 'Drawing image for {0}'.format(d['name'])
im = imread('./mosaics/{0}.png'.format(d['name']))
im = (im*255).astype('uint8')
h, w, _ = im.shape
out[y-floor(h/2.):y+ceil(h/2.), x-floor(w/2.):x+ceil(w/2.), :] = im
render_bubbles(root)
render_image(root, x=out.shape[1]//2, y=out.shape[0]//2)
def downsample(image):
h, w, _ = image.shape
for x in arange(w)[::2]:
if x+1 < w:
image[:,x,:] = ((image[:,x,:].astype('uint16')+image[:,x+1,:].astype('uint16'))//2).astype('uint8')
else:
image[:,x,:] = ((image[:,x,:].astype('uint16')+image[:,x-1,:].astype('uint16'))//2).astype('uint8')
for y in arange(h)[::2]:
if y+1 < h:
image[y,:,:] = ((image[y,:,:].astype('uint16')+image[y+1,:,:].astype('uint16'))//2).astype('uint8')
else:
image[y,:,:] = ((image[y,:,:].astype('uint16')+image[y-1,:,:].astype('uint16'))//2).astype('uint8')
return image[::2,::2]
#############################
## Write tile pyramid to disk
#############################
TILE_SIZE=256
zoom = 0
while 1:
print 'Writing zoom level {0}'.format(zoom)
try:
os.makedirs('pyramid/zoom%d/'%(20-zoom))
except:
pass
h, w, _ = out.shape
nx = int(ceil(w*1.0/TILE_SIZE))
ny = int(ceil(h*1.0/TILE_SIZE))
for i in range(nx):
for j in range(ny):
oi = zeros((TILE_SIZE,TILE_SIZE,3), dtype='uint8')
oi[:,:,:] = 255
x0 = i*TILE_SIZE
y0 = j*TILE_SIZE
x1 = min((i+1)*TILE_SIZE, w)
y1 = min((j+1)*TILE_SIZE, h)
oi[0:y1-y0,0:x1-x0,:] = out[y0:y1,x0:x1,:]
Im = Image.fromarray(oi)
Im.save('pyramid/zoom%d/%d-%d.png'%(20-zoom,i,j))
if nx <= 3 and ny <= 3 and zoom >= 2:
break
zoom += 1
out = downsample(out)