Mercurial > hg > mood-conductor
comparison visualclient2/visclient.py @ 37:2db17c224664
added visclient2
author | gyorgyf |
---|---|
date | Fri, 24 Apr 2015 07:09:32 +1000 |
parents | |
children | 874ac833c8e3 |
comparison
equal
deleted
inserted
replaced
36:caea7ec0c162 | 37:2db17c224664 |
---|---|
1 #!/usr/bin/env python | |
2 # encoding: utf-8 | |
3 """ | |
4 visclient.py | |
5 | |
6 Created by George Fazekas on 2012-06-17. | |
7 Copyright (c) 2012 . All rights reserved. | |
8 """ | |
9 | |
10 import sys,os,math,time,copy | |
11 import pygame as pg | |
12 from pygame.locals import * | |
13 from pygame import gfxdraw as gd | |
14 import httplib as ht | |
15 | |
16 import gradients | |
17 from gradients import genericFxyGradient | |
18 | |
19 from threading import Thread | |
20 from random import random | |
21 | |
22 import colorsys as cs | |
23 | |
24 scol = (0,255,0,255) | |
25 ecol = (0,0,0,255) | |
26 | |
27 xavg=0 | |
28 yavg=0 | |
29 countavg=0 | |
30 moods = [] | |
31 | |
32 | |
33 # X,Y=1140,900 | |
34 # X,Y = 600,400 | |
35 X,Y = 800,600 | |
36 # X,Y = 1024,768 | |
37 | |
38 # Fullscreen resolution: | |
39 # XF,YF = 1280,900 | |
40 # XF,YF = 1440,900 | |
41 XF,YF = 1344,900 | |
42 # display calibrated | |
43 | |
44 # detect display resolution | |
45 import subprocess | |
46 screenres = subprocess.Popen('xrandr | grep "\*" | cut -d" " -f4',shell=True, stdout=subprocess.PIPE).communicate()[0] | |
47 screenres = map(lambda x: int(x.strip()), screenres.split('x')) | |
48 XF,YF = screenres | |
49 print "Screen resolution: ",XF,YF | |
50 | |
51 | |
52 # NBLOBS = 18 | |
53 # BLOBSIZE = 25 | |
54 # G=110 | |
55 # FADE = 15 | |
56 # DIST = 0.15 # blob equivalence tolerance | |
57 # FRAMERATE = 60 | |
58 | |
59 # new parameters | |
60 NBLOBS = 18 | |
61 BLOBSIZE = 25 | |
62 G=110 | |
63 FADE = 25 | |
64 DIST = 0.10 # blob equivalence tolerance | |
65 FRAMERATE = 120 | |
66 | |
67 | |
68 # Connection: | |
69 # IP = "127.0.0.1:8030" | |
70 # IP = "192.168.2.158:8030" | |
71 IP = "138.37.95.215" # this is the IP of kakapo<=>golden | |
72 HTTP_TIMEOUT = 3 | |
73 SERVER_UPDATE_INTERVAL = 0.8 | |
74 | |
75 | |
76 class Indicator(object): | |
77 | |
78 | |
79 def __init__(self,bg,pos,invisible=False): | |
80 | |
81 self.off_color = pg.Color(110,0,0) | |
82 self.on_color = pg.Color(0,120,0) | |
83 if invisible : | |
84 self.off_color = pg.Color(0,0,0) | |
85 self.on_color = pg.Color(0,0,120) | |
86 self.visible = True | |
87 self.ison = True | |
88 self.x,self.y = pos | |
89 self.xs = int(self.x * X) | |
90 self.ys = int(Y - (self.y * Y)) | |
91 self.c = self.off_color | |
92 self.size = 6 | |
93 self.bg = bg | |
94 | |
95 def reinit(self,bg): | |
96 self.bg = bg | |
97 self.xs = int(self.x * X) | |
98 self.ys = int(Y - (self.y * Y)) | |
99 | |
100 def draw(self): | |
101 if self.visible : | |
102 # pg.draw.circle(self.bg, self.c, (self.xs,self.ys),self.size,0) | |
103 # gd.aacircle(self.bg, self.xs, self.ys, self.size+1, self.c) | |
104 gd.filled_circle(self.bg, self.xs, self.ys, self.size, self.c) | |
105 gd.aacircle(self.bg, self.xs, self.ys, self.size, self.c) | |
106 | |
107 | |
108 def toggle(self): | |
109 if self.ison == True : | |
110 self.off() | |
111 else : | |
112 self.on() | |
113 return self | |
114 | |
115 def on(self): | |
116 self.c = self.on_color | |
117 self.ison = True | |
118 return self | |
119 | |
120 def off(self): | |
121 self.c = self.off_color | |
122 self.ison = False | |
123 return self | |
124 | |
125 def now(self,screen): | |
126 # for i in self.indicators.itervalues() : | |
127 # i.draw() | |
128 self.draw() | |
129 screen.blit(self.bg, (0, 0)) | |
130 pg.display.flip() | |
131 return self | |
132 | |
133 class MovingBlob(object): | |
134 | |
135 black_color = pg.Color(0,0,0) | |
136 | |
137 def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): | |
138 self.x = x | |
139 self.y = y | |
140 self.xs = x * X | |
141 self.ys = Y - (y * Y) | |
142 self.bg = bg | |
143 self.size = BLOBSIZE | |
144 self.time = time.time() | |
145 self.alpha = 255 | |
146 self.c = color | |
147 self.count = 1 | |
148 self.visible = True | |
149 self.FADE = fade | |
150 self.mood = mood | |
151 if mood and mood.color : | |
152 self.c = mood.color | |
153 self.title = mood.word | |
154 self.speed_factor = 80.0 | |
155 self.target_x = 0.0 | |
156 self.target_y = 0.0 | |
157 | |
158 def set_target(self,x,y): | |
159 self.target_x = x | |
160 self.target_y = y | |
161 | |
162 def draw(self): | |
163 if not self.visible : return | |
164 | |
165 global xavg,yavg,moods | |
166 # xspeed = (xavg - self.x) / self.speed_factor | |
167 # yspeed = (yavg - self.y) / self.speed_factor | |
168 # self.x = self.x + xspeed | |
169 # self.y = self.y + yspeed | |
170 self.x = self.x + (self.target_x - self.x) / self.speed_factor | |
171 self.y = self.y + (self.target_y - self.y) / self.speed_factor | |
172 | |
173 #self.size = countavg//TODO | |
174 self.xs = self.x * X | |
175 self.ys = Y - (self.y * Y) | |
176 #d=int(self.size) | |
177 | |
178 d = cd = sys.float_info.max | |
179 | |
180 for mood in moods : | |
181 d = mood.get_distance(self.x,self.y) | |
182 if d < cd : | |
183 cd = d | |
184 self.mood = mood | |
185 self.c = mood.color | |
186 self.title = mood.word | |
187 | |
188 # 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-BLOBSIZE,self.ys-BLOBSIZE)) | |
189 d=int(self.size) | |
190 # print d | |
191 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)) | |
192 gd.aacircle(self.bg, int(self.xs), int(self.ys), int(self.size), self.black_color) | |
193 gd.aacircle(self.bg, int(self.xs), int(self.ys), int(self.size-1), self.black_color) | |
194 | |
195 | |
196 font = pg.font.Font(None, 36) | |
197 text = font.render(self.title, 1, self.c) | |
198 textpos = text.get_rect() | |
199 #print textpos.width | |
200 if self.xs > X- textpos.width: | |
201 if self.ys > Y- textpos.height: | |
202 self.bg.blit(text, (self.xs - textpos.width,self.ys - textpos.height)) | |
203 else: | |
204 self.bg.blit(text, (self.xs - textpos.width,self.ys)) | |
205 | |
206 else : | |
207 if self.ys > Y- textpos.height: | |
208 self.bg.blit(text, (self.xs,self.ys - textpos.height)) | |
209 else: | |
210 self.bg.blit(text, (self.xs,self.ys)) | |
211 #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)) | |
212 #self.alpha = 255 - int(self.age()*self.FADE) | |
213 #if self.alpha < 5 : | |
214 # self.alpha = 1 | |
215 # self.visible = False | |
216 | |
217 def age(self): | |
218 return time.time() - self.time | |
219 | |
220 def resize(self,count): | |
221 if self.count == count : | |
222 return None | |
223 self.time = time.time() | |
224 self.count = count | |
225 # self.size = int(BLOBSIZE * int(self.count/1.5)) | |
226 self.to = int(BLOBSIZE * int(self.count/1.5)) | |
227 if self.to < BLOBSIZE : | |
228 self.to = BLOBSIZE | |
229 if self.to and self.size != self.to: | |
230 self.start_animate() | |
231 # print "resize to",count,self.to | |
232 | |
233 def get_distance(self,x,y): | |
234 return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) | |
235 | |
236 def fade(self,fade): | |
237 if not fade : self.alpha = 255 | |
238 self.FADE = fade | |
239 | |
240 def reset_time(self): | |
241 self.time = time.time() | |
242 | |
243 def reinit(self,bg): | |
244 self.bg = bg | |
245 self.xs = int(self.x * X) | |
246 self.ys = int(Y - (self.y * Y)) | |
247 | |
248 def start_animate(self): | |
249 self.thread = Thread(target = self.animate) | |
250 self.thread.daemon = True | |
251 self.thread.start() | |
252 | |
253 def animate(self): | |
254 '''Animate the way bubbles are grown.''' | |
255 tolerance = 5 | |
256 # while self.size > self.to-tolerance and self.size < self.to+tolerance : | |
257 while self.size != self.to : | |
258 self.size = int(self.size + int(self.to-self.size) * 0.1) | |
259 time_inc = 20.0 / (pow(1.2, abs(self.to-self.size)) * 200.0) | |
260 time.sleep(0.02+time_inc) | |
261 # print "sizing to ", self.to | |
262 | |
263 | |
264 class Blob(object): | |
265 | |
266 def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): | |
267 self.x = x | |
268 self.y = y | |
269 self.xs = x * X | |
270 self.ys = Y - (y * Y) | |
271 self.bg = bg | |
272 self.size = BLOBSIZE #pixels | |
273 self.time = time.time() #s | |
274 self.const_time = time.time() #s | |
275 self.alpha = 255 #8-bit alpha channel value | |
276 self.c = color #RGB colour 3-tuple | |
277 self.count = 1 | |
278 self.visible = True | |
279 self.FADE = fade | |
280 if mood and mood.color : | |
281 self.c = mood.color | |
282 self.title = mood.word | |
283 self.inactivity_delay = 75.0 #s | |
284 self.proximity_delay = 35.0 #s | |
285 self.proximity_tolerance = 0.13 | |
286 self.target_in_proximity = None | |
287 | |
288 def __cmp__(self,other): | |
289 if other is None : | |
290 return -1 | |
291 d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) | |
292 if d < DIST : | |
293 return 0 | |
294 else : | |
295 return -1 | |
296 | |
297 def object_in_proximity(self,other): | |
298 if other is None : | |
299 return False | |
300 d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) | |
301 if d < self.proximity_tolerance : | |
302 return True | |
303 else : | |
304 return False | |
305 | |
306 def check_target_proximity(self,target): | |
307 '''Check if the moving bubble is in proximity of this object. As soon as it is there, mark the time. | |
308 We do not want to reset this time just wait until this bubble dies due to inactivity in its region.''' | |
309 prox = self.object_in_proximity(target) | |
310 if self.target_in_proximity is None and prox is True : | |
311 self.target_in_proximity = time.time() | |
312 # if prox is False : | |
313 # self.target_in_proximity = None | |
314 | |
315 def draw(self): | |
316 if not self.visible : return | |
317 | |
318 d=int(self.size) | |
319 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)) | |
320 self.alpha = 255 - int(self.age()*self.FADE) | |
321 if self.alpha < 5 : | |
322 self.alpha = 1 | |
323 self.visible = False | |
324 | |
325 def age(self): | |
326 return time.time() - self.time | |
327 | |
328 def real_age(self): | |
329 return time.time() - self.const_time | |
330 | |
331 def age_weight(self): | |
332 age_s = time.time() - self.const_time | |
333 if age_s < self.inactivity_delay : | |
334 return 1.0 | |
335 return 1.0 / ((age_s-self.inactivity_delay+1)*10.0) | |
336 | |
337 def proximity_weight(self): | |
338 if self.target_in_proximity == None : | |
339 return 1.0 | |
340 age_s = time.time() - self.target_in_proximity | |
341 if age_s < self.proximity_delay : | |
342 return 1.0 | |
343 return 1.0 / ((age_s-self.proximity_delay+1)*10.0) | |
344 | |
345 def increment(self,count): | |
346 self.time = time.time() | |
347 self.count = count | |
348 # self.size = int(BLOBSIZE * int(self.count/1.5)) | |
349 self.to = int(BLOBSIZE * int(self.count/1.5)) | |
350 if self.to < 250 : | |
351 self.start_animate() | |
352 | |
353 def get_distance(self,x,y): | |
354 return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) | |
355 | |
356 def fade(self,fade): | |
357 if not fade : self.alpha = 255 | |
358 self.FADE = fade | |
359 | |
360 def reset_time(self): | |
361 self.time = time.time() | |
362 | |
363 def start_animate(self): | |
364 self.thread = Thread(target = self.animate) | |
365 self.thread.daemon = True | |
366 self.thread.start() | |
367 | |
368 def animate(self): | |
369 '''Animate the way bubbles are grown.''' | |
370 while self.size < self.to : | |
371 self.size += 1 | |
372 time_inc = 20.0 / (pow(1.2, self.to-self.size) * 200.0) | |
373 time.sleep(0.02+time_inc) | |
374 | |
375 def force(self,other): | |
376 '''Calculate the force between this object and another''' | |
377 if other is None : | |
378 return 0.0,0.0,False | |
379 captured = False | |
380 ds = math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) | |
381 if ds < 0.005 : | |
382 return 0.0,0.0,False | |
383 d = math.sqrt(ds) | |
384 if d < 0.07 : | |
385 captured = True | |
386 # return 0.0,0.0,False | |
387 m1,m2 = self.size,other.size | |
388 G = 6.674 * 0.000001 | |
389 f = - G * (m1*m2) / ds | |
390 x = f * (other.x-self.x) / d | |
391 y = f * (other.y-self.y) / d | |
392 return x,y,captured | |
393 | |
394 | |
395 class BlobTrail(object): | |
396 | |
397 def __init__(self,bg,x,y,color=(255,255,255),mood=None,fade=FADE): | |
398 self.x = x | |
399 self.y = y | |
400 self.xs = x * X | |
401 self.ys = Y - (y * Y) | |
402 self.bg = bg | |
403 self.size = BLOBSIZE | |
404 self.time = time.time() | |
405 self.alpha = 255 | |
406 self.c = color | |
407 self.count = 1 | |
408 self.visible = True | |
409 self.FADE = fade | |
410 if mood and mood.color : | |
411 self.c = mood.color | |
412 self.title = mood.word | |
413 | |
414 def __cmp__(self,other): | |
415 d = math.sqrt( math.pow((self.x-other.x),2) + math.pow((self.y-other.y),2) ) | |
416 if d < DIST : | |
417 return 0 | |
418 else : | |
419 return -1 | |
420 | |
421 def draw(self): | |
422 if not self.visible : return | |
423 | |
424 d=int(self.size) | |
425 self.bg.blit(gradients.radial(self.size, (self.c[0],self.c[1],self.c[2],self.alpha), (0,0,0,0)), (self.xs-d,self.ys-d)) | |
426 self.alpha = 255 - int(self.age()*self.FADE) | |
427 if self.alpha < 5 : | |
428 self.alpha = 1 | |
429 self.visible = False | |
430 | |
431 def age(self): | |
432 return time.time() - self.time | |
433 | |
434 def increment(self,count): | |
435 self.time = time.time() | |
436 self.count = count | |
437 # self.size = int(BLOBSIZE * int(self.count/1.5)) | |
438 self.to = int(BLOBSIZE * int(self.count/1.5)) | |
439 self.start_animate() | |
440 | |
441 def get_distance(self,x,y): | |
442 return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) | |
443 | |
444 def fade(self,fade): | |
445 if not fade : self.alpha = 255 | |
446 self.FADE = fade | |
447 | |
448 def reset_time(self): | |
449 self.time = time.time() | |
450 | |
451 def start_animate(self): | |
452 self.thread = Thread(target = self.animate) | |
453 self.thread.daemon = True | |
454 self.thread.start() | |
455 | |
456 def animate(self): | |
457 '''Animate the way bubbles are grown.''' | |
458 while self.size < self.to : | |
459 self.size += 1 | |
460 time_inc = 20.0 / (pow(1.2, self.to-self.size) * 200.0) | |
461 time.sleep(0.02+time_inc) | |
462 | |
463 | |
464 class Mood(): | |
465 | |
466 def __init__(self,word,x,y): | |
467 self.word = word | |
468 self.x = (float(x)/ 2.0) + 0.5 | |
469 self.y = (float(y)/ 2.0) + 0.5 | |
470 self.color = [] | |
471 | |
472 def get_distance(self,x,y): | |
473 return math.sqrt( math.pow((self.x-x),2) + math.pow((self.y-y),2) ) | |
474 | |
475 def __repr__(self): | |
476 return "Mood(%s,%3.2f,%3.2f)" %(self.word,self.x,self.y) | |
477 | |
478 | |
479 | |
480 class VisualClient(object): | |
481 '''Main visualisation client.''' | |
482 global moods,blobTrail | |
483 | |
484 def __init__(self): | |
485 # self.conn = ht.HTTPConnection("192.168.2.184:8030") | |
486 # self.conn = ht.HTTPConnection("138.37.95.215") | |
487 self.s_age = 10 | |
488 self.s_dist = DIST | |
489 self.s_ninp = 18 | |
490 | |
491 pg.init() | |
492 pg.font.init() | |
493 | |
494 # fontObj = pg.font.Font("freesansbold.ttf",18) | |
495 white = ( 255, 255, 255) | |
496 black = ( 0,0,0) | |
497 | |
498 self.fpsClock = pg.time.Clock() | |
499 self.screen = pg.display.set_mode((X, Y)) | |
500 pg.display.set_caption('Mood Conductor') | |
501 self.bg = pg.Surface(self.screen.get_size()) | |
502 self.bg = self.bg.convert() | |
503 self.bg.fill((0,0,0)) | |
504 pg.display.set_gamma(100.0) | |
505 | |
506 | |
507 self.scol = (0,255,0,255) | |
508 self.ecol = (0,0,0,255) | |
509 coordstxt = "test" | |
510 | |
511 self.blobs = [] | |
512 #self.moods = [] | |
513 self.bt=[] | |
514 | |
515 self.movingBlob = None | |
516 | |
517 self.read_mood_data() | |
518 | |
519 self.FADE = FADE | |
520 | |
521 self.indicators = { | |
522 "conn":Indicator(self.bg,(0.98,0.02)), # connection active | |
523 "update":Indicator(self.bg,(0.96,0.02)), # update thread executing | |
524 "data":Indicator(self.bg,(0.94,0.02)), # data status changed | |
525 "receive":Indicator(self.bg,(0.92,0.02)), # data received | |
526 "grow":Indicator(self.bg,(0.90,0.02)), # blob growth active | |
527 "ignore":Indicator(self.bg,(0.88,0.02),True), # little AI: ignore some clusters in certain condition | |
528 "suspend":Indicator(self.bg,(0.86,0.02),True), # prevent adding new blobs (key: d) | |
529 "config":Indicator(self.bg,(0.84,0.02),True), # sending config data | |
530 "fade":Indicator(self.bg,(0.82,0.02),True)} # fade on/off (key: s) | |
531 | |
532 self.thread = None | |
533 self.running = False | |
534 self.fullscreen = False | |
535 self.init_reconnect = False | |
536 self.suspend = False | |
537 | |
538 pass | |
539 | |
540 | |
541 def read_mood_data(self): | |
542 '''Read the mood position and color information form csv file.''' | |
543 # file = 'moods.csv' | |
544 file = '../tags/mc_moodtags_lfm_curated2.csv' | |
545 with open(file) as mf: | |
546 data = mf.readlines()[1:] | |
547 for line in data : | |
548 l = line.split(',') | |
549 l = map(lambda x:x.replace("'","").strip(),l) | |
550 mood = Mood(l[0],l[1],l[2]) | |
551 moods.append(mood) | |
552 print moods | |
553 for mood in moods: | |
554 print"['%s',%2.2f,%2.2f,0,0]," %(mood.word,mood.x,mood.y) | |
555 | |
556 with open('colors.txt') as ff: | |
557 data = ff.readlines()[1:] | |
558 data = map(lambda x: x.split(','),data) | |
559 for mood in moods : | |
560 d = cd = sys.float_info.max | |
561 for colors in data : | |
562 d = mood.get_distance(float(colors[0]),float(colors[1])) | |
563 if d < cd : | |
564 cd = d | |
565 # mood.color = tuple(map(lambda x: int(pow(math.atan((float(x)/7.0)),12.5)),(colors[2],colors[3],colors[4]))) | |
566 mood.color = self.set_color(tuple(map(lambda x: int(x),(colors[2],colors[3],colors[4])))) | |
567 return True | |
568 | |
569 def set_color(self,color): | |
570 '''Move to HLS colour space and manipulate saturation there.''' | |
571 # TODO: ideally, we need a non-linear compressor of the lightness and saturation values | |
572 r,g,b = map(lambda x: (1.0*x/255.0), color) | |
573 h,l,s = cs.rgb_to_hls(r,g,b) | |
574 s = 1.0 #1.0 - (1.0 / pow(50.0,s)) | |
575 l = 1.0 - (1.0 / pow(20.0,l)) #0.6 | |
576 r,g,b = map(lambda x: int(x*255), cs.hls_to_rgb(h,l,s)) | |
577 return r,g,b | |
578 | |
579 def start_update_thread(self): | |
580 '''Start the thread that reads data from the server.''' | |
581 time.sleep(SERVER_UPDATE_INTERVAL) | |
582 self.thread = Thread(target = self.update_thread) | |
583 self.thread.daemon = True | |
584 self.running = True | |
585 self.thread.start() | |
586 print "OK. Update thread started." | |
587 | |
588 def stop_update_thread(self): | |
589 '''Stop the thread and allow some time fot the connections to close.''' | |
590 self.running = False | |
591 try : | |
592 self.thread.join(2) | |
593 self.indicators["conn"].off() | |
594 self.indicators["update"].off() | |
595 except : | |
596 print "No update thread to join." | |
597 | |
598 def update_thread(self): | |
599 '''The server update thread''' | |
600 print "Thread reporting..." | |
601 while self.running : | |
602 # self.update() | |
603 try : | |
604 self.update() | |
605 # self.indicators["update"].visible = True | |
606 except Exception, e: | |
607 if str(e).strip() : print "Exception: ", str(e), type(e), len(str(e).strip()) | |
608 self.indicators["conn"].off() | |
609 # self.indicators["update"].visible = False | |
610 try : | |
611 time.sleep(SERVER_UPDATE_INTERVAL) | |
612 except : | |
613 if str(e).strip() : print "Exception: ", str(e), type(e), len(str(e).strip()) | |
614 self.indicators["conn"].off() | |
615 | |
616 | |
617 def update(self): | |
618 '''Update the blob list from the server. This should be in a thread.''' | |
619 # global xavg, yavg, countavg | |
620 # indicate connection health by toggling an indictor | |
621 self.indicators["update"].toggle() | |
622 | |
623 # delete invisibles | |
624 for blob in self.blobs : | |
625 if not blob.visible : | |
626 self.blobs.remove(blob) | |
627 | |
628 # get new coordinates from the server | |
629 self.conn.putrequest("GET","/moodconductor/result", skip_host=True) | |
630 self.conn.putheader("Host", "www.isophonics.net") | |
631 self.conn.endheaders() | |
632 res = self.conn.getresponse() | |
633 data = res.read() | |
634 data = eval(data) | |
635 if not data : | |
636 self.conn.close() | |
637 self.indicators["data"].toggle() | |
638 return False | |
639 | |
640 tempx = 0 | |
641 tempy = 0 | |
642 tempcount = 0 | |
643 | |
644 for d in data : | |
645 # coordstxt = "x:%s y:%s c:%s" %d | |
646 x,y,count = d | |
647 | |
648 tempx = tempx + x*count | |
649 tempy = tempy + y*count | |
650 tempcount = tempcount + count | |
651 | |
652 self.add_blob(x,y,count) | |
653 self.indicators["receive"].toggle() | |
654 | |
655 xavg = tempx/tempcount | |
656 yavg = tempy/tempcount | |
657 countavg = tempcount/len(data) | |
658 # print xavg, yavg, countavg | |
659 # if not self.blobs : | |
660 # self.add_blob(xavg,yavg,countavg) | |
661 # self.indicators["receive"].toggle() | |
662 | |
663 self.conn.close() | |
664 self.blobs = self.blobs[:NBLOBS] | |
665 return True | |
666 | |
667 | |
668 def add_blob(self,x,y,count=1): | |
669 '''Insert a blob to the list of blobs''' | |
670 # find mood correxponding to x,y | |
671 if self.suspend : | |
672 return None | |
673 cmood = None | |
674 d = cd = sys.float_info.max | |
675 for mood in moods : | |
676 d = mood.get_distance(x,y) | |
677 if d < cd : | |
678 cd = d | |
679 cmood = mood | |
680 # create new blob or increase click count on existing one | |
681 new = Blob(self.bg,x,y,mood=cmood,fade=self.FADE) | |
682 if self.movingBlob == None : | |
683 self.movingBlob = MovingBlob(self.bg,x,y,mood=cmood,fade=self.FADE) | |
684 if not new in self.blobs : | |
685 self.blobs.insert(0,new) | |
686 elif count > self.blobs[self.blobs.index(new)].count: | |
687 self.blobs[self.blobs.index(new)].increment(count) | |
688 self.indicators["grow"].toggle() | |
689 pass | |
690 | |
691 | |
692 def draw(self): | |
693 '''Draw all objects''' | |
694 global xavg, yavg, countavg | |
695 | |
696 self.bg.fill((0,0,0,1)) | |
697 # self.bg.blit(gradients.radial(19, self.scol, self.ecol), (rect_x,rect_y)) | |
698 forces = [] | |
699 l = copy.copy(self.blobs) | |
700 l.reverse() | |
701 xt,yt = 0.0,0.0 | |
702 bs = 1 | |
703 c = 1 | |
704 # captured_by = None | |
705 | |
706 # calculate exponential weighted average of the visible blobs | |
707 ignore = False | |
708 for blob in l : | |
709 blob.draw() | |
710 c = c + blob.count | |
711 # aw = blob.age_weight() | |
712 aw = blob.proximity_weight() | |
713 if aw < 1.0 : ignore = True | |
714 w = math.pow(blob.size+(blob.alpha/2.0),7) * aw | |
715 xt = xt + blob.x * w | |
716 yt = yt + blob.y * w | |
717 bs = bs + w | |
718 if self.movingBlob != None : | |
719 blob.check_target_proximity(self.movingBlob) | |
720 xavg = xt / bs | |
721 yavg = yt / bs | |
722 # countavg = bs/(len(l)+1) | |
723 countavg = int(c/(len(l)+1)) | |
724 if ignore : | |
725 self.indicators["ignore"].on() | |
726 else : | |
727 self.indicators["ignore"].off() | |
728 | |
729 # compute gravity force | |
730 # if self.movingBlob != None : | |
731 # x,y,c = blob.force(self.movingBlob) | |
732 # forces.append((x,y)) | |
733 # if c : captured_by = blob | |
734 # tx,ty = reduce(lambda a,b:(a[0]+b[0],a[1]+b[1]), forces, (0.5,0.5)) | |
735 | |
736 # print tx,ty | |
737 # if tx>1.0 : tx = 1.0 | |
738 # if tx<0.0 : tx = 0.0 | |
739 # if ty>1.0 : ty = 1.0 | |
740 # if ty<0.0 : ty = 0.0 | |
741 | |
742 # xavg,yavg = tx,ty | |
743 | |
744 # if tx <= 1.0 and tx >= 0.0 and ty <= 1.0 and ty >= 0.0 : | |
745 # xavg,yavg = tx,ty | |
746 # countavg = 15 | |
747 # print tx,ty | |
748 # else : | |
749 # print "out of bounds:",tx,ty | |
750 # if captured_by != None : | |
751 # xavg,yavg = captured_by.x,captured_by.y | |
752 l = copy.copy(self.bt) | |
753 l.reverse() | |
754 for trail in l: | |
755 trail.draw() | |
756 if self.movingBlob != None : | |
757 # self.movingBlob.resize(countavg) | |
758 self.movingBlob.set_target(xavg,yavg) | |
759 self.movingBlob.draw() | |
760 new = BlobTrail(self.bg,self.movingBlob.x,self.movingBlob.y,mood=self.movingBlob.mood,fade=18) | |
761 self.bt.insert(0,new) | |
762 # axis | |
763 pg.draw.line(self.bg, (G,G,G), (int(X/2.0),0),(int(X/2.0),Y), 1) | |
764 pg.draw.line(self.bg, (G,G,G), (0,int(Y/2.0)),(X,int(Y/2.0)), 1) | |
765 | |
766 # indicators | |
767 for i in self.indicators.itervalues() : | |
768 i.draw() | |
769 | |
770 def read_keys(self): | |
771 '''Read keys''' | |
772 for event in pg.event.get() : | |
773 # Quit (event) | |
774 if event.type == QUIT: | |
775 self.quit() | |
776 elif event.type == KEYDOWN : | |
777 # Post Quit: Esc, q | |
778 if event.key == K_ESCAPE or event.key == K_q : | |
779 pg.event.post(pg.event.Event(QUIT)) | |
780 # Reset: Space | |
781 elif event.key == K_SPACE : | |
782 if not self.blobs : | |
783 self.bt = [] | |
784 self.blobs = [] | |
785 # Random : r | |
786 elif event.key == K_r : | |
787 self.add_blob(random(),random(),count=5) | |
788 # Connection : c | |
789 elif event.key == K_c : | |
790 self.init_reconnect = True | |
791 self.indicators["conn"].off() | |
792 # Fullscreen: f | |
793 elif event.key == K_f : | |
794 # pg.display.toggle_fullscreen() | |
795 self.toggle_screen_mode() | |
796 # Toggle suspend: d | |
797 elif event.key == K_d : | |
798 if self.suspend : | |
799 print "suspend off" | |
800 self.indicators["suspend"].off() | |
801 self.suspend = False | |
802 else: | |
803 print "suspend on" | |
804 self.indicators["suspend"].on() | |
805 self.suspend = True | |
806 # Toggle fade: s | |
807 elif event.key == K_s : | |
808 if self.FADE > 0: | |
809 print "fade off" | |
810 self.indicators["fade"].off() | |
811 self.FADE = 0 | |
812 for blob in self.blobs : | |
813 blob.fade(0) | |
814 else: | |
815 print "fade on" | |
816 self.indicators["fade"].on() | |
817 self.FADE = FADE | |
818 for blob in self.blobs : | |
819 blob.fade(FADE) | |
820 blob.reset_time() | |
821 # inc age | |
822 elif event.key == K_1 : | |
823 self.s_age += 1 | |
824 self.update_server_config(self.s_age,self.s_dist,self.s_ninp) | |
825 # dec age | |
826 elif event.key == K_2 : | |
827 self.s_age -= 1 | |
828 self.update_server_config(self.s_age,self.s_dist,self.s_ninp) | |
829 # inc dist | |
830 elif event.key == K_3 : | |
831 self.s_dist += 0.025 | |
832 self.update_server_config(self.s_age,self.s_dist,self.s_ninp) | |
833 # dec dist | |
834 elif event.key == K_4 : | |
835 self.s_dist -= 0.025 | |
836 if self.s_dist < 0.025 : self.s_dist = 0.025 | |
837 self.update_server_config(self.s_age,self.s_dist,self.s_ninp) | |
838 # inc ninp | |
839 elif event.key == K_5 : | |
840 self.s_ninp += 1 | |
841 self.update_server_config(self.s_age,self.s_dist,self.s_ninp) | |
842 # dec ninp | |
843 elif event.key == K_6 : | |
844 self.s_ninp -= 1 | |
845 if self.s_ninp < 2 : self.s_ninp = 2 | |
846 self.update_server_config(self.s_age,self.s_dist,self.s_ninp) | |
847 # choose different app and restart server | |
848 elif event.key == K_9 : | |
849 self.choose_app() | |
850 | |
851 pass | |
852 pass | |
853 | |
854 def toggle_screen_mode(self): | |
855 '''Go back and forth between full screen mode.''' | |
856 if self.fullscreen == False: | |
857 globals()['_X'] = globals()['X'] | |
858 globals()['_Y'] = globals()['Y'] | |
859 globals()['X'] = XF | |
860 globals()['Y'] = YF | |
861 self.screen = pg.display.set_mode((X, Y)) | |
862 # self.screen = pg.display.set_mode((0,0),pg.FULLSCREEN) | |
863 self.fullscreen = True | |
864 self.bg = pg.Surface(self.screen.get_size()) | |
865 self.bg = self.bg.convert() | |
866 self.bg.fill((0,0,0)) | |
867 for i in self.indicators.itervalues() : | |
868 i.reinit(self.bg) | |
869 i.draw() | |
870 if self.movingBlob != None : | |
871 self.movingBlob.reinit(self.bg) | |
872 self.movingBlob.draw() | |
873 else : | |
874 globals()['X'] = globals()['_X'] | |
875 globals()['Y'] = globals()['_Y'] | |
876 self.screen = pg.display.set_mode((X, Y)) | |
877 self.fullscreen = False | |
878 self.bg = pg.Surface(self.screen.get_size()) | |
879 self.bg = self.bg.convert() | |
880 self.bg.fill((0,0,0)) | |
881 for i in self.indicators.itervalues() : | |
882 i.reinit(self.bg) | |
883 i.draw() | |
884 if self.movingBlob != None : | |
885 self.movingBlob.reinit(self.bg) | |
886 self.movingBlob.draw() | |
887 pg.display.toggle_fullscreen() | |
888 | |
889 | |
890 | |
891 def run(self): | |
892 | |
893 # setup connection | |
894 self.connect() | |
895 | |
896 # main loop | |
897 while True : | |
898 # pg.draw.circle(screen, pg.Color(255,0,0), (300,50),20,0) | |
899 # screen.blit(gradients.radial(99, scol, ecol), (401, 1)) | |
900 | |
901 self.read_keys() | |
902 | |
903 # Draw | |
904 self.draw() | |
905 | |
906 # update display | |
907 self.screen.blit(self.bg, (0, 0)) | |
908 pg.display.flip() | |
909 self.fpsClock.tick(FRAMERATE) | |
910 | |
911 if self.init_reconnect: | |
912 self.reconnect() | |
913 | |
914 return True | |
915 | |
916 def choose_app(self): | |
917 '''Experimental function for chaging served apps remotely. Disabled for now...''' | |
918 return False | |
919 try : | |
920 print "Changing app and restarting... the connection will be lost." | |
921 self.conn.putrequest("GET","/moodconductor/changeapp", skip_host=True) | |
922 self.conn.putheader("Host", "www.isophonics.net") | |
923 self.conn.endheaders() | |
924 res = self.conn.getresponse() | |
925 res.read() | |
926 except : | |
927 pass | |
928 | |
929 def configure_server(self): | |
930 '''Send the server some configuration data.''' | |
931 # age = 10.0 | |
932 # dist = DIST | |
933 # ninp = 18 | |
934 self.update_server_config(self.s_age,self.s_dist,self.s_ninp) | |
935 | |
936 | |
937 def update_server_config(self,age,dist,ninp,retry = 3): | |
938 '''Send the server some configuration data.''' | |
939 self.indicators["config"].on().now(self.screen) | |
940 try : | |
941 print "Sending configuration data." | |
942 self.conn.putrequest("GET","/moodconductor/config?age=%(age)s&dist=%(dist)s&ninp=%(ninp)s" %locals(), skip_host=True) | |
943 self.conn.putheader("Host", "www.isophonics.net") | |
944 self.conn.endheaders() | |
945 res = self.conn.getresponse() | |
946 res.read() | |
947 if not res.status == 200 : | |
948 print "Server response:", res.status, res.reason | |
949 self.indicators["conn"].off() | |
950 time.sleep(0.5) | |
951 self.conn.putrequest("GET","/moodconductor/getconf", skip_host=True) | |
952 self.conn.putheader("Host", "www.isophonics.net") | |
953 self.conn.endheaders() | |
954 res = self.conn.getresponse() | |
955 if not res.status == 200 : | |
956 print "Server response:", res.status, res.reason | |
957 self.indicators["conn"].off() | |
958 # self.indicators["config"].on() | |
959 print "Server configuration:", res.read() | |
960 self.indicators["config"].off() | |
961 self.indicators["conn"].on() | |
962 except: | |
963 print "Failed to send configuration data." | |
964 time.sleep(2) | |
965 retry -= 1 | |
966 self.update_server_config(age,dist,ninp,retry) | |
967 | |
968 | |
969 def connect(self,retry = 5): | |
970 '''Connect to the server and test connection.''' | |
971 if not retry : | |
972 print "Server unreachable" | |
973 pg.quit() | |
974 raise SystemExit | |
975 if retry < 5 : | |
976 time.sleep(3) | |
977 try : | |
978 print "connecting to server..." | |
979 self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) | |
980 self.indicators["conn"].on() | |
981 except : | |
982 self.indicators["conn"].off() | |
983 self.connect(retry = retry-1) | |
984 print "connection failed." | |
985 | |
986 try: | |
987 print "Testing connection." | |
988 # self.conn.putrequest("GET","/moodconductor/index.html", skip_host=True) | |
989 self.conn.putrequest("GET","/moodconductor/test", skip_host=True) | |
990 self.conn.putheader("Host", "www.isophonics.net") | |
991 self.conn.endheaders() | |
992 res = self.conn.getresponse() | |
993 print res.read() | |
994 if res.status == 200 : | |
995 self.indicators["conn"].on() | |
996 else : | |
997 print "Server response:", res.status, res.reason | |
998 self.indicators["conn"].off() | |
999 if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : | |
1000 return False | |
1001 else : | |
1002 print "Failed. retrying." | |
1003 self.noretry = None | |
1004 self.connect(retry = retry-1) | |
1005 except Exception as e: | |
1006 print "Exception while testing connection.", e | |
1007 self.indicators["conn"].off() | |
1008 # comment out in offline mode | |
1009 if not hasattr(self,"noretry") and raw_input("Go offline? ") in ['y',''] : | |
1010 return False | |
1011 else : | |
1012 self.noretry = None | |
1013 self.connect(retry = retry-1) | |
1014 self.configure_server() | |
1015 print "OK. Starting update thread..." | |
1016 self.start_update_thread() | |
1017 return True | |
1018 | |
1019 | |
1020 | |
1021 def reconnect(self): | |
1022 '''Called when c is pressed.''' | |
1023 self.indicators["config"].on().now(self.screen) | |
1024 self.init_reconnect = False | |
1025 | |
1026 self.stop_update_thread() | |
1027 time.sleep(1) | |
1028 try : | |
1029 self.conn.close() | |
1030 except : | |
1031 self.indicators["conn"].off() | |
1032 try : | |
1033 self.conn = ht.HTTPConnection(IP,timeout=HTTP_TIMEOUT) | |
1034 self.conn.connect() | |
1035 self.indicators["conn"].on() | |
1036 print "Reconnected." | |
1037 except : | |
1038 self.indicators["conn"].off() | |
1039 print "Error while reconnecting." | |
1040 self.start_update_thread() | |
1041 self.indicators["config"].off().now(self.screen) | |
1042 | |
1043 | |
1044 def quit(self): | |
1045 print "Quitting.." | |
1046 self.indicators["conn"].off() | |
1047 self.stop_update_thread() | |
1048 self.conn.close() | |
1049 pg.quit() | |
1050 sys.exit() | |
1051 | |
1052 | |
1053 def main(): | |
1054 | |
1055 v = VisualClient() | |
1056 v.run() | |
1057 | |
1058 if __name__ == '__main__': | |
1059 pass | |
1060 main() | |
1061 |