Mercurial > hg > mood-conductor
comparison visualclient/visclient.py @ 27:c97feb7ef9e9
Strasbourg
author | gyorgyf |
---|---|
date | Thu, 20 Sep 2012 23:55:48 +0100 |
parents | 858fc44a17c1 |
children | 6022a370c7dd |
comparison
equal
deleted
inserted
replaced
26:2ec521bbd543 | 27:c97feb7ef9e9 |
---|---|
12 from pygame.locals import * | 12 from pygame.locals import * |
13 import httplib as ht | 13 import httplib as ht |
14 | 14 |
15 import gradients | 15 import gradients |
16 from gradients import genericFxyGradient | 16 from gradients import genericFxyGradient |
17 | |
18 from threading import Thread | |
19 from random import random | |
17 | 20 |
18 # from pytagcloud import create_tag_image, make_tags | 21 # from pytagcloud import create_tag_image, make_tags |
19 # from pytagcloud.lang.counter import get_tag_counts | 22 # from pytagcloud.lang.counter import get_tag_counts |
20 | 23 |
21 # YOUR_TEXT = "A tag cloud is a visual representation for text data, typically\ | 24 # YOUR_TEXT = "A tag cloud is a visual representation for text data, typically\ |
27 | 30 |
28 scol = (0,255,0,255) | 31 scol = (0,255,0,255) |
29 ecol = (0,0,0,255) | 32 ecol = (0,0,0,255) |
30 | 33 |
31 # X,Y=1140,900 | 34 # X,Y=1140,900 |
32 X,Y = 600,400 | 35 # X,Y = 600,400 |
33 NBLOBS = 15 | 36 X,Y = 800,600 |
37 | |
38 NBLOBS = 18 | |
34 BLOBSIZE = 25 | 39 BLOBSIZE = 25 |
35 G=110 | 40 G=110 |
36 FADE = 15 | 41 FADE = 15 |
42 DIST = 0.1 # blob equivalence tolerance | |
43 FRAMERATE = 60 | |
44 | |
45 # Connection: | |
46 # IP = "192.168.2.184:8030" | |
47 IP = "138.37.95.215" | |
48 HTTP_TIMEOUT = 3 | |
49 SERVER_UPDATE_INTERVAL = 0.8 | |
50 | |
51 class Indicator(object): | |
52 | |
53 off_color = pg.Color(110,0,0) | |
54 on_color = pg.Color(0,120,0) | |
55 | |
56 def __init__(self,bg,pos): | |
57 self.visible = True | |
58 self.ison = True | |
59 self.x,self.y = pos | |
60 self.xs = int(self.x * X) | |
61 self.ys = int(Y - (self.y * Y)) | |
62 self.c = self.off_color | |
63 self.size = 6 | |
64 self.bg = bg | |
65 | |
66 def reinit(self,bg): | |
67 self.bg = bg | |
68 self.xs = int(self.x * X) | |
69 self.ys = int(Y - (self.y * Y)) | |
70 | |
71 def draw(self): | |
72 if self.visible : | |
73 pg.draw.circle(self.bg, self.c, (self.xs,self.ys),self.size,0) | |
74 | |
75 def toggle(self): | |
76 if self.ison == True : | |
77 self.off() | |
78 else : | |
79 self.on() | |
80 return self | |
81 | |
82 def on(self): | |
83 self.c = self.on_color | |
84 self.ison = True | |
85 return self | |
86 | |
87 def off(self): | |
88 self.c = self.off_color | |
89 self.ison = False | |
90 return self | |
91 | |
37 | 92 |
38 class Blob(object): | 93 class Blob(object): |
39 | 94 |
40 def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): | 95 def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): |
41 self.x = x | 96 self.x = x |
53 if mood and mood.color : | 108 if mood and mood.color : |
54 self.c = mood.color | 109 self.c = mood.color |
55 | 110 |
56 def __cmp__(self,other): | 111 def __cmp__(self,other): |
57 d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) | 112 d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) |
58 if d < 0.03 : | 113 if d < DIST : |
59 return 0 | 114 return 0 |
60 else : | 115 else : |
61 return -1 | 116 return -1 |
62 | 117 |
63 def draw(self): | 118 def draw(self): |
119 if not self.visible : return | |
64 d=int(self.size) | 120 d=int(self.size) |
65 self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-d,self.ys-d)) | 121 self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,self.alpha)), (self.xs-d,self.ys-d)) |
66 self.alpha = 255 - int(self.age()*self.FADE) | 122 self.alpha = 255 - int(self.age()*self.FADE) |
67 if self.alpha < 5 : | 123 if self.alpha < 5 : |
68 self.alpha = 1 | 124 self.alpha = 1 |
100 return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) | 156 return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) |
101 | 157 |
102 | 158 |
103 | 159 |
104 class VisualClient(object): | 160 class VisualClient(object): |
161 '''Main visualisation client.''' | |
105 | 162 |
106 def __init__(self): | 163 def __init__(self): |
107 # self.conn = ht.HTTPConnection("192.168.2.184:8030") | 164 # self.conn = ht.HTTPConnection("192.168.2.184:8030") |
108 self.conn = ht.HTTPConnection("138.37.95.215") | 165 # self.conn = ht.HTTPConnection("138.37.95.215") |
109 | 166 |
110 pg.init() | 167 pg.init() |
111 | 168 |
112 # fontObj = pg.font.Font("freesansbold.ttf",18) | 169 # fontObj = pg.font.Font("freesansbold.ttf",18) |
113 white = ( 255, 255, 255) | 170 white = ( 255, 255, 255) |
117 self.screen = pg.display.set_mode((X, Y)) | 174 self.screen = pg.display.set_mode((X, Y)) |
118 pg.display.set_caption('Mood Conductor') | 175 pg.display.set_caption('Mood Conductor') |
119 self.bg = pg.Surface(self.screen.get_size()) | 176 self.bg = pg.Surface(self.screen.get_size()) |
120 self.bg = self.bg.convert() | 177 self.bg = self.bg.convert() |
121 self.bg.fill((0,0,0)) | 178 self.bg.fill((0,0,0)) |
179 pg.display.set_gamma(100.0) | |
122 | 180 |
123 | 181 |
124 self.scol = (0,255,0,255) | 182 self.scol = (0,255,0,255) |
125 self.ecol = (0,0,0,255) | 183 self.ecol = (0,0,0,255) |
126 coordstxt = "test" | 184 coordstxt = "test" |
127 | 185 |
128 self.blobs = [] | 186 self.blobs = [] |
129 self.moods = [] | 187 self.moods = [] |
130 self.read_mood_data() | 188 self.read_mood_data() |
131 # pg.quit() | |
132 # sys.exit(-1) | |
133 | 189 |
134 self.FADE = FADE | 190 self.FADE = FADE |
135 | 191 |
192 self.indicators = { | |
193 "conn":Indicator(self.bg,(0.98,0.02)), | |
194 "update":Indicator(self.bg,(0.96,0.02)), | |
195 "data":Indicator(self.bg,(0.94,0.02)), | |
196 "receive":Indicator(self.bg,(0.92,0.02))} | |
197 | |
198 self.thread = None | |
199 self.running = False | |
200 self.fullscreen = False | |
201 self.init_reconnect = False | |
136 pass | 202 pass |
137 | 203 |
204 | |
138 def read_mood_data(self): | 205 def read_mood_data(self): |
139 | 206 '''Read the mood position and color information form csv file.''' |
140 with open('moods.csv') as mf: | 207 with open('moods.csv') as mf: |
141 data = mf.readlines()[1:] | 208 data = mf.readlines()[1:] |
142 for line in data : | 209 for line in data : |
143 l = line.split(',') | 210 l = line.split(',') |
144 mood = Mood(l[0],l[1],l[2]) | 211 mood = Mood(l[0],l[1],l[2]) |
145 self.moods.append(mood) | 212 self.moods.append(mood) |
146 # with open('feelings.txt') as ff: | |
147 # data = ff.readlines() | |
148 # data = map(lambda x: x.split('\t'),data) | |
149 # for mood in self.moods : | |
150 # for colors in data : | |
151 # if mood.word == colors[0] : | |
152 # mood.color = colors[2] | |
153 with open('colors.txt') as ff: | 213 with open('colors.txt') as ff: |
154 data = ff.readlines()[1:] | 214 data = ff.readlines()[1:] |
155 data = map(lambda x: x.split(','),data) | 215 data = map(lambda x: x.split(','),data) |
156 for mood in self.moods : | 216 for mood in self.moods : |
157 d = cd = sys.float_info.max | 217 d = cd = sys.float_info.max |
158 for colors in data : | 218 for colors in data : |
159 d = mood.get_distance(float(colors[0]),float(colors[1])) | 219 d = mood.get_distance(float(colors[0]),float(colors[1])) |
160 if d < cd : | 220 if d < cd : |
161 cd = d | 221 cd = d |
162 mood.color = tuple(map(lambda x: int(pow(math.atan((float(x)/7.0)),12.5)),(colors[2],colors[3],colors[4]))) | 222 mood.color = tuple(map(lambda x: int(pow(math.atan((float(x)/7.0)),12.5)),(colors[2],colors[3],colors[4]))) |
163 | 223 # mood.color = tuple(map(lambda x: int(x),(colors[2],colors[3],colors[4]))) |
164 # for mood in self.moods: | 224 return True |
165 # if mood.color : | 225 |
166 # # mood.color = map(lambda x: '0x'+str(x).strip(),[mood.color[0:2],mood.color[2:4],mood.color[4:]]) | 226 |
167 # mood.color = tuple(map(lambda x: int(x),mood.color)) | 227 def start_update_thread(self): |
168 # print mood.color | 228 '''Start the thread that reads data from the server.''' |
169 | 229 self.running = True |
230 self.thread = Thread(target = self.update_thread) | |
231 self.thread.daemon = True | |
232 self.thread.start() | |
233 | |
234 def stop_update_thread(self): | |
235 '''Stop the thread and allow some time fot the connections to close.''' | |
236 self.running = False | |
237 try : | |
238 self.thread.join(2) | |
239 except : | |
240 print "No update thread to join." | |
241 | |
242 def update_thread(self): | |
243 '''The server update thread''' | |
244 while self.running : | |
245 try : | |
246 self.update() | |
247 # self.indicators["update"].visible = True | |
248 except Exception, e: | |
249 if str(e).strip() : print "Exception: ", str(e), type(e), len(str(e).strip()) | |
250 self.indicators["conn"].off() | |
251 # self.indicators["update"].visible = False | |
252 time.sleep(SERVER_UPDATE_INTERVAL) | |
253 | |
170 | 254 |
171 def update(self): | 255 def update(self): |
256 '''Update the blob list from the server. This should be in a thread.''' | |
257 | |
258 # indicate connection health by toggling an indictor | |
259 self.indicators["update"].toggle() | |
172 | 260 |
173 # delete invisibles | 261 # delete invisibles |
174 for blob in self.blobs : | 262 for blob in self.blobs : |
175 if not blob.visible : | 263 if not blob.visible : |
176 self.blobs.remove(blob) | 264 self.blobs.remove(blob) |
179 self.conn.putrequest("GET","/moodconductor/result", skip_host=True) | 267 self.conn.putrequest("GET","/moodconductor/result", skip_host=True) |
180 self.conn.putheader("Host", "www.isophonics.net") | 268 self.conn.putheader("Host", "www.isophonics.net") |
181 self.conn.endheaders() | 269 self.conn.endheaders() |
182 res = self.conn.getresponse() | 270 res = self.conn.getresponse() |
183 data = res.read() | 271 data = res.read() |
184 data = eval(data) | 272 data = eval(data) |
185 if not data : | 273 if not data : |
186 self.conn.close() | 274 self.conn.close() |
275 self.indicators["data"].toggle() | |
187 return False | 276 return False |
188 for d in data : | 277 for d in data : |
189 # coordstxt = "x:%s y:%s c:%s" %d | 278 # coordstxt = "x:%s y:%s c:%s" %d |
190 x,y,c = d | 279 x,y,count = d |
191 | 280 self.add_blob(x,y,count) |
192 cmood = None | 281 self.indicators["receive"].toggle() |
193 d = cd = sys.float_info.max | |
194 for mood in self.moods : | |
195 d = mood.get_distance(x,y) | |
196 if d < cd : | |
197 cd = d | |
198 cmood = mood | |
199 | |
200 new = Blob(self.bg,x,y,mood=cmood,fade=self.FADE) | |
201 if not new in self.blobs : | |
202 self.blobs.insert(0,new) | |
203 elif c > self.blobs[self.blobs.index(new)].count: | |
204 self.blobs[self.blobs.index(new)].increment(c) | |
205 | |
206 self.conn.close() | 282 self.conn.close() |
207 self.blobs = self.blobs[:NBLOBS] | 283 self.blobs = self.blobs[:NBLOBS] |
208 return True | 284 return True |
285 | |
286 | |
287 def add_blob(self,x,y,count=1): | |
288 '''Insert a blob to the list of blobs''' | |
289 # find mood correxponding to x,y | |
290 cmood = None | |
291 d = cd = sys.float_info.max | |
292 for mood in self.moods : | |
293 d = mood.get_distance(x,y) | |
294 if d < cd : | |
295 cd = d | |
296 cmood = mood | |
297 # create new blob or increase click count on existing one | |
298 new = Blob(self.bg,x,y,mood=cmood,fade=self.FADE) | |
299 if not new in self.blobs : | |
300 self.blobs.insert(0,new) | |
301 elif count > self.blobs[self.blobs.index(new)].count: | |
302 self.blobs[self.blobs.index(new)].increment(count) | |
303 pass | |
209 | 304 |
210 | 305 |
211 def draw(self): | 306 def draw(self): |
212 self.bg.fill((0,0,0)) | 307 self.bg.fill((0,0,0)) |
213 # self.bg.blit(gradients.radial(19, self.scol, self.ecol), (rect_x,rect_y)) | 308 # self.bg.blit(gradients.radial(19, self.scol, self.ecol), (rect_x,rect_y)) |
214 l = copy.copy(self.blobs) | 309 l = copy.copy(self.blobs) |
215 l.reverse() | 310 l.reverse() |
216 for blob in l : | 311 for blob in l : |
217 blob.draw() | 312 blob.draw() |
218 | 313 |
219 # axis | 314 # axis |
220 pg.draw.line(self.bg, (G,G,G), (int(X/2.0),0),(int(X/2.0),Y), 1) | 315 pg.draw.line(self.bg, (G,G,G), (int(X/2.0),0),(int(X/2.0),Y), 1) |
221 pg.draw.line(self.bg, (G,G,G), (0,int(Y/2.0)),(X,int(Y/2.0)), 1) | 316 pg.draw.line(self.bg, (G,G,G), (0,int(Y/2.0)),(X,int(Y/2.0)), 1) |
222 | 317 |
318 # indicators | |
319 for i in self.indicators.itervalues() : | |
320 i.draw() | |
321 | |
322 def read_keys(self): | |
323 '''Read keys''' | |
324 for event in pg.event.get() : | |
325 # Quit (event) | |
326 if event.type == QUIT: | |
327 self.quit() | |
328 elif event.type == KEYDOWN : | |
329 # Post Quit: Esc, q | |
330 if event.key == K_ESCAPE or event.key == K_q : | |
331 pg.event.post(pg.event.Event(QUIT)) | |
332 # Reset: Space | |
333 elif event.key == K_SPACE : | |
334 self.blobs = [] | |
335 # Random : r | |
336 elif event.key == K_r : | |
337 self.add_blob(random(),random(),count=5) | |
338 # Connection : c | |
339 elif event.key == K_c : | |
340 self.init_reconnect = True | |
341 self.indicators["conn"].off() | |
342 # Fullscreen: f | |
343 elif event.key == K_f : | |
344 # pg.display.toggle_fullscreen() | |
345 self.toggle_screen_mode() | |
346 # Toggle fade: s | |
347 elif event.key == K_s : | |
348 if self.FADE : | |
349 print "fade off" | |
350 self.indicators["conn"].off() | |
351 self.FADE = 0 | |
352 for blob in self.blobs : | |
353 blob.fade(0) | |
354 else: | |
355 print "fade on" | |
356 self.indicators["conn"].on() | |
357 self.FADE = 15 | |
358 for blob in self.blobs : | |
359 blob.fade(15) | |
360 blob.reset_time() | |
361 pass | |
362 | |
363 def toggle_screen_mode(self): | |
364 '''Go back and forth between full screen mode.''' | |
365 if self.fullscreen == False: | |
366 globals()['_X'] = globals()['X'] | |
367 globals()['_Y'] = globals()['Y'] | |
368 globals()['X'] = 1440 | |
369 globals()['Y'] = 900 | |
370 self.screen = pg.display.set_mode((X, Y)) | |
371 self.fullscreen = True | |
372 self.bg = pg.Surface(self.screen.get_size()) | |
373 self.bg = self.bg.convert() | |
374 self.bg.fill((0,0,0)) | |
375 for i in self.indicators.itervalues() : | |
376 i.reinit(self.bg) | |
377 i.draw() | |
378 else : | |
379 globals()['X'] = globals()['_X'] | |
380 globals()['Y'] = globals()['_Y'] | |
381 self.screen = pg.display.set_mode((X, Y)) | |
382 self.fullscreen = False | |
383 self.bg = pg.Surface(self.screen.get_size()) | |
384 self.bg = self.bg.convert() | |
385 self.bg.fill((0,0,0)) | |
386 for i in self.indicators.itervalues() : | |
387 i.reinit(self.bg) | |
388 i.draw() | |
389 pg.display.toggle_fullscreen() | |
390 | |
223 | 391 |
224 | 392 |
225 def run(self): | 393 def run(self): |
226 # conn = ht.HTTPConnection("192.168.2.184:8030") | 394 |
227 self.conn = ht.HTTPConnection("138.37.95.215") | 395 # setup connection |
228 | 396 self.connect() |
229 rect_x,rect_y = 0,0 | 397 |
230 counter = 0 | |
231 | |
232 # main loop | 398 # main loop |
233 while True : | 399 while True : |
234 # pg.draw.circle(screen, pg.Color(255,0,0), (300,50),20,0) | 400 # pg.draw.circle(screen, pg.Color(255,0,0), (300,50),20,0) |
235 # screen.blit(gradients.radial(99, scol, ecol), (401, 1)) | 401 # screen.blit(gradients.radial(99, scol, ecol), (401, 1)) |
236 | 402 |
237 for event in pg.event.get() : | 403 self.read_keys() |
238 if event.type == QUIT: | |
239 self.conn.close() | |
240 pg.quit() | |
241 sys.exit() | |
242 elif event.type == KEYDOWN : | |
243 if event.key == K_ESCAPE : | |
244 pg.event.post(pg.event.Event(QUIT)) | |
245 elif event.key == K_SPACE : | |
246 self.blobs = [] | |
247 elif event.key == K_s : | |
248 if self.FADE : | |
249 print "fade off" | |
250 self.FADE = 0 | |
251 for blob in self.blobs : | |
252 blob.fade(0) | |
253 else: | |
254 print "fade on" | |
255 self.FADE = 15 | |
256 for blob in self.blobs : | |
257 blob.fade(15) | |
258 blob.reset_time() | |
259 | |
260 | |
261 | |
262 | 404 |
263 # Draw | 405 # Draw |
264 self.draw() | 406 self.draw() |
265 | 407 |
266 | |
267 # update from the server | |
268 counter += 1 | |
269 if counter % 12 : | |
270 counter = 0 | |
271 try : | |
272 self.update() | |
273 except: | |
274 pass | |
275 # update display | 408 # update display |
276 self.screen.blit(self.bg, (0, 0)) | 409 self.screen.blit(self.bg, (0, 0)) |
277 pg.display.flip() | 410 pg.display.flip() |
278 self.fpsClock.tick(50) | 411 self.fpsClock.tick(FRAMERATE) |
279 pass | 412 |
413 if self.init_reconnect: | |
414 self.reconnect() | |
415 | |
416 return True | |
417 | |
418 | |
419 def connect(self,retry = 5): | |
420 '''Connect to the server and test connection.''' | |
421 if not retry : | |
422 print "Server unreachable" | |
423 pg.quit() | |
424 raise SystemExit | |
425 if retry < 5 : | |
426 time.sleep(3) | |
427 try : | |
428 self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) | |
429 # self.start_update_thread() | |
430 self.indicators["conn"].on() | |
431 except : | |
432 self.indicators["conn"].off() | |
433 self.connect(retry = retry-1) | |
434 | |
435 try: | |
436 self.conn.putrequest("GET","/moodconductor/index.html", skip_host=True) | |
437 self.conn.putheader("Host", "www.isophonics.net") | |
438 self.conn.endheaders() | |
439 res = self.conn.getresponse() | |
440 if res.status == 200 : | |
441 self.indicators["conn"].on() | |
442 else : | |
443 print "Server response:", res.status, res.reason | |
444 self.indicators["conn"].off() | |
445 if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : | |
446 return False | |
447 else : | |
448 self.noretry = None | |
449 self.connect(retry = retry-1) | |
450 except : | |
451 print "Exception while testing connection." | |
452 self.indicators["conn"].off() | |
453 # comment out in offline mode | |
454 if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : | |
455 return False | |
456 else : | |
457 self.noretry = None | |
458 self.connect(retry = retry-1) | |
459 self.start_update_thread() | |
460 return True | |
461 | |
462 | |
463 | |
464 def reconnect(self): | |
465 '''Called when c is pressed.''' | |
466 self.init_reconnect = False | |
467 # self.indicators["conn"].off().draw() | |
468 # self.screen.blit(self.bg, (0, 0)) | |
469 # pg.display.flip() | |
470 | |
471 self.stop_update_thread() | |
472 time.sleep(1) | |
473 try : | |
474 self.conn.close() | |
475 except : | |
476 self.indicators["conn"].off() | |
477 try : | |
478 self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) | |
479 self.conn.connect() | |
480 self.indicators["conn"].on() | |
481 print "Reconnected." | |
482 except : | |
483 self.indicators["conn"].off() | |
484 print "Error while reconnecting." | |
485 self.start_update_thread() | |
486 | |
487 | |
488 def quit(self): | |
489 print "Quitting.." | |
490 self.indicators["conn"].off() | |
491 self.stop_update_thread() | |
492 self.conn.close() | |
493 pg.quit() | |
494 sys.exit() | |
495 | |
496 | |
497 | |
280 | 498 |
281 | 499 |
282 def main(): | 500 def main(): |
283 | 501 |
284 v = VisualClient() | 502 v = VisualClient() |