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