source: trunk/Puzzlebox/Synapse/Session.py @ 398

Last change on this file since 398 was 398, checked in by sc, 8 years ago
  • data export fixes
File size: 20.5 KB
Line 
1# -*- coding: utf-8 -*-
2
3# Copyright Puzzlebox Productions, LLC (2010-2012)
4#
5# This code is released under the GNU Pulic License (GPL) version 2
6# For more information please refer to http://www.gnu.org/copyleft/gpl.html
7
8__changelog__ = """\
9Last Update: 2012.05.07
10"""
11
12__todo__ = """
13"""
14
15### IMPORTS ###
16import os, sys, time
17
18import Puzzlebox.Synapse.Configuration as configuration
19
20if configuration.ENABLE_PYSIDE:
21        try:
22                import PySide as PyQt4
23                from PySide import QtCore, QtGui
24        except Exception, e:
25                print "ERROR: [Synapse:Session] Exception importing PySide:",
26                print e
27                configuration.ENABLE_PYSIDE = False
28        else:
29                print "INFO: [Synapse:Session] Using PySide module"
30
31if not configuration.ENABLE_PYSIDE:
32        print "INFO: [Synapse:Session] Using PyQt4 module"
33        from PyQt4 import QtCore, QtGui
34
35
36try:
37        import cPickle as pickle
38except:
39        import pickle
40
41
42#####################################################################
43# Globals
44#####################################################################
45
46DEBUG = configuration.DEBUG
47
48DEFAULT_SIGNAL_LEVEL_MESSAGE = \
49        {"poorSignalLevel": 0}
50                # A quantifier of the quality of the brainwave signal.
51                # This is an integer value that is generally in the
52                # range of 0 to 200, with 0 indicating a
53                # good signal and 200 indicating an off-head state.
54
55DEFAULT_EEG_POWER_MESSAGE = \
56        {"eegPower": { \
57                'delta': 0, \
58                'theta': 0, \
59                'lowAlpha': 0, \
60                'highAlpha': 0, \
61                'lowBeta': 0, \
62                'highBeta': 0, \
63                'lowGamma': 0, \
64                'highGamma': 0, \
65                }, \
66        } # A container for the EEG powers. These may
67          # be either integer or floating-point values.
68          # Maximum values are undocumented but assumed to be 65535
69
70DEFAULT_ESENSE_MESSAGE = \
71        {"eSense": { \
72                'attention': 0, \
73                'meditation': 0, \
74                }, \
75        } # A container for the eSense™ attributes.
76          # These are integer values between 0 and 100,
77          # where 0 is perceived as a lack of that attribute
78          # and 100 is an excess of that attribute.
79
80DEFAULT_BLINK_MESSAGE = {"blinkStrength": 255}
81        # The strength of a detected blink. This is
82        # an integer in the range of 0-255.
83
84DEFAULT_RAWEEG_MESSAGE = {"rawEeg": 255}
85        # The raw data reading off the forehead sensor.
86        # This may be either an integer or a floating-point value.
87
88DEFAULT_PACKET = {}
89DEFAULT_PACKET.update(DEFAULT_EEG_POWER_MESSAGE)
90DEFAULT_PACKET.update(DEFAULT_SIGNAL_LEVEL_MESSAGE)
91DEFAULT_PACKET.update(DEFAULT_ESENSE_MESSAGE)
92
93PACKET_MINIMUM_TIME_DIFFERENCE_THRESHOLD = 0.75
94
95#####################################################################
96# Classes
97#####################################################################
98
99class puzzlebox_synapse_session(QtGui.QWidget):
100       
101        def __init__(self, log, \
102                     DEBUG=DEBUG, \
103                     parent=None, \
104                     ):
105               
106                self.log = log
107                self.DEBUG = DEBUG
108                self.parent=parent
109               
110                if self.parent == None:
111                        QtGui.QWidget.__init__(self, parent)
112                        #self.setupUi(self)
113               
114                        self.configureSettings()
115                        self.connectWidgets()
116               
117                self.name = "Synapse:Session"
118               
119               
120                if (sys.platform == 'win32'):
121                        self.homepath = os.path.join( \
122                           os.environ['HOMEDRIVE'], \
123                           os.environ['HOMEPATH'], \
124                           'Desktop')
125                elif (sys.platform == 'darwin'):
126                        desktop = os.path.join(os.environ['HOME'], 'Documents')
127                        if os.path.exists(desktop):
128                                self.homepath = desktop
129                        else:
130                                self.homepath = os.environ['HOME']
131                else:
132                        desktop = os.path.join(os.environ['HOME'], 'Desktop')
133                        if os.path.exists(desktop):
134                                self.homepath = desktop
135                        else:
136                                self.homepath = os.environ['HOME']
137               
138               
139                if not os.path.exists(self.homepath):
140                        if self.DEBUG:
141                                print "WARNING: [Synapse:Session] User default path not found"
142                        self.homepath = os.getcwd()
143       
144       
145        ##################################################################
146       
147        def configureSettings(self):
148               
149                pass
150       
151       
152        ##################################################################
153       
154        def connectWidgets(self):
155               
156                pass
157       
158       
159        ##################################################################
160       
161        def updateProfileSessionStatus(self, source=None, target=None):
162               
163                session_time = self.calculateSessionTime()
164               
165                #if source == None:
166                        #if self.parent == None:
167                                #source = self
168                        #else:
169                                #source = self.parent
170               
171                #if target == None:
172                        #if self.parent == None:
173                                #target = self
174                        #else:
175                                #target = self.parent
176               
177                #target.textLabelSessionTime.setText(session_time)
178                self.textLabelSessionTime.setText(session_time)
179               
180                        #self.parent.packet_count)
181                        #self.synapseServer.protocol.packet_count)
182               
183                try:
184                        packet_count = self.parent.plugin_eeg.getPacketCount()
185                except:
186                        try:
187                                packet_count = self.synapseServer.protocol.packet_count
188                        except:
189                                packet_count = 0
190               
191                self.textLabelPacketsReceived.setText( "%i" % packet_count)
192               
193               
194                try:
195                        bad_packets = self.parent.plugin_eeg.getBadPackets()
196                except:
197                        try:
198                                bad_packets = self.synapseServer.protocol.bad_packets
199                        except:
200                                bad_packets = 0
201               
202                self.textLabelPacketsDropped.setText( "%i" % bad_packets)
203       
204       
205        ##################################################################
206       
207        def calculateSessionTime(self):
208               
209                session_time = self.getSessionTime()
210               
211                session_time = time.time() - session_time
212                session_time = int(session_time)
213                session_time = self.convert_seconds_to_datetime(session_time)
214               
215                return (session_time)
216       
217       
218        ##################################################################
219       
220        def getSessionTime(self):
221               
222                return (self.synapseServer.session_start_timestamp)
223       
224       
225        ##################################################################
226       
227        def collectData(self, source=None, target=None):
228               
229                if source == None:
230                        if self.parent == None:
231                                source = self
232                        else:
233                                source = self.parent
234               
235                if target == None:
236                        if self.parent == None:
237                                target = self
238                        else:
239                                target = self.parent
240               
241                data = {}
242               
243                data['rawEeg'] = source.packets['rawEeg']
244                data['signals'] = source.packets['signals']
245               
246                data['sessionTime'] = self.calculateSessionTime()
247               
248                data['profileName'] = str(target.lineEditSessionProfile.text())
249               
250                return(data)
251       
252       
253        ##################################################################
254       
255        def parseTimeStamp(self, timestamp, local_version=False, truncate_time_zone=False):
256               
257                try:
258                        decimal = '%f' % timestamp
259                        decimal = decimal.split('.')[1]
260                except:
261                        decimal = '0'
262               
263                localtime = time.localtime(timestamp)
264               
265                if local_version:
266                        date = time.strftime('%x', localtime)
267                        localtime = time.strftime('%X', localtime)
268               
269                elif truncate_time_zone:
270                        date = time.strftime('%Y-%m-%d', localtime)
271                        localtime = time.strftime('%H:%M:%S', localtime)
272                        localtime = '%s.%s' % (localtime, decimal[:3])
273               
274                else:
275                        date = time.strftime('%Y-%m-%d', localtime)
276                        localtime = time.strftime('%H:%M:%S', localtime)
277                        localtime = '%s.%s %s' % (localtime, decimal, \
278                                       time.strftime('%Z', time.localtime(timestamp)))
279               
280               
281                return(date, localtime)
282       
283       
284        ##################################################################
285       
286        def saveData(self, source=None, target=None, output_file=None, use_default=False):
287               
288                if source == None:
289                        if self.parent == None:
290                                source = self
291                        else:
292                                source = self.parent
293               
294                if target == None:
295                        if self.parent == None:
296                                target = self
297                        else:
298                                target = self.parent
299               
300                data = self.collectData(source=source, target=target)
301               
302                (date, localtime) = self.parseTimeStamp(time.time())
303               
304                default_filename = '%s %s.synapse' % (date, \
305                                      target.lineEditSessionProfile.text())
306                                     
307                default_filename = os.path.join(self.homepath, default_filename)
308               
309                if output_file == None:
310                       
311                        # use_default controls whether or not a file is automatically saves using the
312                        # default name and path (as opposed to raising a GUI file selection menu)
313                        # whenever an explicit filepath is not defined
314                        if use_default:
315                                       
316                                        output_file = default_filename
317                       
318                        else:
319                       
320                                output_file = QtGui.QFileDialog.getSaveFileName(parent=target, \
321                                                 caption="Save Session Data to File", \
322                                                 dir=default_filename, \
323                                                 filter="Puzzlebox Synapse Data File (*.synapse)")
324                               
325                                try:
326                                        output_file = output_file[0]
327                                except:
328                                        output_file = ''
329               
330               
331                if output_file == '':
332                        return
333               
334                file = open(str(output_file), 'w')
335                pickle.dump(data, file)
336                file.close()
337       
338       
339        ##################################################################
340       
341        def exportData(self, parent=None, source=None, target=None, output_file=None, use_default=False):
342               
343                if parent == None:
344                        if self.parent == None:
345                                parent = self
346                        else:
347                                parent = self.parent
348               
349                if source == None:
350                        if self.parent == None:
351                                source = self
352                        else:
353                                source = self.parent
354               
355                if target == None:
356                        if self.parent == None:
357                                target = self
358                        else:
359                                target = self.parent
360               
361               
362                try:
363                        export_csv_raw = target.configuration.EXPORT_CSV_RAW_DATA
364                except:
365                        export_csv_raw = False
366               
367               
368                (date, localtime) = self.parseTimeStamp(time.time())
369               
370                default_filename = '%s %s.csv' % (date, \
371                                      target.lineEditSessionProfile.text())
372               
373                default_filename = os.path.join(target.homepath, default_filename)
374               
375               
376                if output_file == None:
377                       
378                        # use_default controls whether or not a file is automatically saves using the
379                        # default name and path (as opposed to raising a GUI file selection menu)
380                        # whenever an explicit filepath is not defined
381                        if use_default:
382                                       
383                                        output_file = default_filename
384                       
385                        else:
386                                output_file = QtGui.QFileDialog.getSaveFileName(parent=target, \
387                                                 caption="Export Session Data to File", \
388                                                 dir=default_filename, \
389                                                 filter="CSV File (*.csv);;Text File (*.txt)")
390                               
391                                try:
392                                        output_file = output_file[0]
393                                except:
394                                        output_file = ''
395               
396               
397                if output_file == '':
398                        return
399               
400               
401                if str(output_file).endswith('.csv'):
402                       
403                        outputData = self.exportDataToCSV(parent=parent, source=source, target=target)
404               
405               
406                else:
407                       
408                        try:
409                                outputData = self.textEditDebugConsole.toPlainText()
410                        except:
411                                outputData = self.exportDataToCSV()
412               
413               
414                file = open(str(output_file), 'w')
415                file.write(outputData)
416                file.close()
417               
418               
419                if export_csv_raw:
420                       
421                        output_file = output_file.replace('.csv', '-rawEeg.csv')
422                       
423                        outputData = self.exportRawDataToCSV(parent=parent, source=source, target=target)
424                       
425                        if outputData != None:
426                               
427                                file = open(str(output_file), 'w')
428                                file.write(outputData)
429                                file.close()
430       
431       
432        ##################################################################
433       
434        def exportDataToCSV(self, parent=None, source=None, target=None):
435               
436                # handle importing class from multiple sources
437                if parent == None:
438                        if self.parent == None:
439                                parent = self
440                        else:
441                                parent = self.parent
442               
443                if source == None:
444                        if self.parent == None:
445                                source = self
446                        else:
447                                source = self.parent
448               
449                if target == None:
450                        if self.parent == None:
451                                target = self
452                        else:
453                                target = self.parent
454               
455                try:
456                        truncate_csv_timezone = target.configuration.EXPORT_CSV_TRUNCATE_TIMEZONE
457                except:
458                        truncate_csv_timezone = False
459               
460               
461                # NOTE: no need to scrub emulated data
462                try:
463                        scrub_data = target.configuration.EXPORT_CSV_SCRUB_DATA
464                except:
465                        scrub_data = False
466               
467                try:
468                        if self.parent.plugin_eeg.emulate_headset_data:
469                                scrub_data = False
470                except:
471                        pass
472               
473               
474                headers = 'Date,Time'
475               
476               
477                customDataHeaders = []
478                for header in parent.customDataHeaders:
479                        customDataHeaders.append(header)
480                for plugin in parent.activePlugins:
481                        print plugin.name
482                        for header in plugin.customDataHeaders:
483                                customDataHeaders.append(header)
484               
485                for each in customDataHeaders:
486                        headers = headers + ',%s' % each
487               
488                headers = headers + '\n'
489               
490               
491                csv = {}
492               
493                for packet in source.packets['signals']:
494                       
495                       
496                        # NOTE: Move this to ThinkGear Server object
497                        if 'rawEeg' in packet.keys():
498                                continue
499                       
500                       
501                        if packet['timestamp'] not in csv.keys():
502                               
503                                #if 'blinkStrength' in packet.keys():
504                                        ## Skip any blink packets from log
505                                        #continue
506                               
507                               
508                                timestamp = packet['timestamp']
509                                #(date, localtime) = self.parseTimeStamp(timestamp, \
510                                                    #truncate_time_zone=truncate_csv_timezone)
511                                (date, localtime) = source.parseTimeStamp(timestamp, \
512                                                    truncate_time_zone=truncate_csv_timezone)
513                               
514                                csv[timestamp] = {}
515                                csv[timestamp]['Date'] = date
516                                csv[timestamp]['Time'] = localtime
517                               
518                               
519                                for plugin in parent.activePlugins:
520                                        if plugin.customDataHeaders != []:
521                                                try:
522                                                        csv[timestamp] = plugin.processPacketForExport(output=csv[timestamp], packet=packet)
523                                                except Exception, e:
524                                                        if self.DEBUG:
525                                                                print "ERROR: [Synapse:Session] Exception calling processPacketForExport on",
526                                                                print plugin.name
527                       
528                       
529                        for header in customDataHeaders:
530                               
531                                #if header in packet.keys():
532                                        #csv[timestamp][header] = packet[header]
533                               
534                                if 'custom' in packet.keys() and \
535                                   header in packet['custom'].keys():
536                                        csv[timestamp][header] = packet['custom'][header]
537               
538               
539                if scrub_data:
540                        csv = self.scrubData(csv, truncate_csv_timezone, source=source)
541               
542               
543                output = headers
544               
545                timestamps = csv.keys()
546                timestamps.sort()
547               
548                for timestamp in timestamps:
549                       
550                        row = '%s,%s' % \
551                              (csv[timestamp]['Date'], \
552                               csv[timestamp]['Time'])
553                       
554                        for header in customDataHeaders:
555                                if header in csv[timestamp].keys():
556                                        row = row + ',%s' % csv[timestamp][header]
557                                else:
558                                        #row = row + ','
559                                        row = ''
560                                        if self.DEBUG:
561                                                print "WARN: empty signals packet:,"
562                                                print csv[timestamp]
563                                        break
564                       
565                        if row != '':
566                                row = row + '\n'
567                       
568                        output = output + row
569               
570               
571                return(output)
572       
573       
574        ##################################################################
575       
576        def exportRawDataToCSV(self, parent=None, source=None, target=None):
577               
578                # handle importing class from multiple sources
579                if parent == None:
580                        if self.parent == None:
581                                parent = self
582                        else:
583                                parent = self.parent
584               
585                if source == None:
586                        if self.parent == None:
587                                source = self
588                        else:
589                                source = self.parent
590               
591                if target == None:
592                        if self.parent == None:
593                                target = self
594                        else:
595                                target = self.parent
596               
597               
598                if source.packets['rawEeg'] == []:
599                        return(None)
600               
601               
602                headers = 'Date,Time,rawEeg'
603               
604                csv = {}
605               
606                for packet in source.packets['rawEeg']:
607                       
608                        # NOTE: Move this to ThinkGear Server object
609                        if 'rawEeg' in packet.keys():
610                               
611                                if packet['timestamp'] not in csv.keys():
612                                       
613                                        timestamp = packet['timestamp']
614                                       
615                                        (date, localtime) = source.parseTimeStamp(timestamp, \
616                                                            truncate_time_zone=truncate_csv_timezone)
617                                       
618                                        csv[timestamp] = {}
619                                        csv[timestamp]['Date'] = date
620                                        csv[timestamp]['Time'] = localtime
621                                        csv[timestamp]['rawEeg'] = packet['rawEeg']
622               
623               
624                output = headers
625               
626                timestamps = csv.keys()
627                timestamps.sort()
628               
629                for timestamp in timestamps:
630                       
631                        row = '%s,%s,%s' % \
632                              (csv[timestamp]['Date'], \
633                               csv[timestamp]['Time'], \
634                               csv[timestamp]['rawEeg'])
635                       
636                        row = row + '\n'
637                       
638                        output = output + row
639               
640               
641                return(output)
642       
643       
644        #################################################################
645       
646        def scrubData(self, csv, truncate_csv_timezone=False, source=None):
647               
648                # If there are missing packets, repeat a given packet once per missing
649                # second until there is a gap between 1 and 2 seconds, in which case
650                # produce a final duplicate packet at the mid-point between the packets
651               
652                if self.DEBUG:
653                        print "INFO: Scrubbing Data"
654               
655                if source == None:
656                        if self.parent == None:
657                                source = self
658                        else:
659                                source = self.parent
660               
661                last_time = None
662                last_recorded_time = None
663               
664                output = {}
665               
666                csv_keys = csv.keys()
667                csv_keys.sort()
668               
669                for key in csv_keys:
670                       
671                        timestamp = key
672                       
673                        if last_time == None:
674                                # First entry in log
675                                last_time = timestamp
676                                last_recorded_time = timestamp
677                                #output[key] = csv[key]
678                                if key not in output.keys():
679                                        output[key] = DEFAULT_PACKET.copy()
680                                output[key].update(csv[key])
681                                continue
682                       
683                        else:
684                               
685                                #time_difference = timestamp - last_time
686                                #time_difference = timestamp - last_recorded_time
687                                time_difference = abs(timestamp - last_recorded_time)
688                               
689                                if (time_difference <= 1) and \
690                                   (time_difference >= PACKET_MINIMUM_TIME_DIFFERENCE_THRESHOLD):
691                                        # Skip packets within the correct time threshold
692                                        last_time = timestamp
693                                        last_recorded_time = timestamp
694                                        #output[key] = csv[key]
695                                        if key not in output.keys():
696                                                output[key] = DEFAULT_PACKET.copy()
697                                        output[key].update(csv[key])
698                                       
699                                        #print "<=1 and >=min"
700                                        continue
701                               
702                                else:
703                                       
704                                        if self.DEBUG > 1:
705                                                print "time_difference:",
706                                                print time_difference
707                                                print "timestamp:",
708                                                print source.parseTimeStamp(timestamp)[-1].split(' ')[0]
709                                                print "last_time:",
710                                                print source.parseTimeStamp(last_time)[-1].split(' ')[0]
711                                                print "last_recorded_time:",
712                                                print source.parseTimeStamp(last_recorded_time)[-1].split(' ')[0]
713                                       
714                                       
715                                        #new_packet = csv[key].copy()
716                                        if key not in output.keys():
717                                                new_packet = DEFAULT_PACKET.copy()
718                                        new_packet.update(csv[key])
719                                       
720                                        if time_difference >= 2:
721                                               
722                                                ##new_time = last_time + 1
723                                                #new_time = last_recorded_time + 1
724                                               
725                                                count = int(time_difference)
726                                                while count >= 1:
727                                                        #new_packet = csv[key].copy()
728                                                        if key not in output.keys():
729                                                                new_packet = DEFAULT_PACKET.copy()
730                                                        new_packet.update(csv[key])
731                                                       
732                                                        new_time = last_recorded_time + 1
733                                                        (date, formatted_new_time) = source.parseTimeStamp(new_time, \
734                                                         truncate_time_zone=truncate_csv_timezone)
735                                                        new_packet['Time'] = formatted_new_time
736                                                        last_recorded_time = new_time
737                                                        last_time = timestamp
738                                                        if key not in output.keys():
739                                                                output[new_time] = new_packet
740                                                        else:
741                                                                output[new_time].update(new_packet)
742                                                        count = count - 1
743                                                continue
744                                       
745                                                #print ">=2"
746                                       
747                                       
748                                        elif time_difference < PACKET_MINIMUM_TIME_DIFFERENCE_THRESHOLD:
749                                                # Spread out "bunched up" packets
750                                                #new_time = last_time + 1
751                                                new_time = last_recorded_time + 1
752                                                #new_time = last_recorded_time
753                                                #print "<min"
754                                       
755                                       
756                                        elif (time_difference < 2) and (time_difference > 1):
757                                               
758                                                #new_time = last_time + ((last_time - timestamp) / 2)
759                                                #new_time = last_recorded_time + ((last_recorded_time - timestamp) / 2)
760                                                #new_time = last_time + 1
761                                                #new_time = last_recorded_time + 1
762                                                new_time = last_recorded_time
763                                                #print "<2"
764                                       
765                                       
766                                        (date, formatted_new_time) = source.parseTimeStamp(new_time, \
767                                           truncate_time_zone=truncate_csv_timezone)
768                                       
769                                        new_packet['Time'] = formatted_new_time
770                                       
771                                        #last_time = new_time
772                                        last_recorded_time = new_time
773                                        #last_time = timestamp
774                                        last_time = new_time
775                                        try:
776                                                output[new_time].update(new_packet)
777                                        except Exception, e:
778                                                output[new_time] = new_packet
779                                                #print e
780                                       
781                                        if self.DEBUG > 1:
782                                                print "WARN: Scrubbing new packet:",
783                                                print new_packet
784                                                print
785               
786               
787                return(output)
788       
789       
790        ##################################################################
791       
792        def resetData(self, source=None):
793               
794                if source == None:
795                        if self.parent == None:
796                                source = self
797                        else:
798                                source = self.parent
799               
800                source.packets['rawEeg'] = []
801                source.packets['signals'] = []
802               
803                if self.synapseServer != None:
804                        self.synapseServer.protocol.resetSessionStartTime()
805                else:
806                        self.resetSessionStartTime()
807               
808                if self.synapseServer != None:
809                        source.synapseServer.protocol.packet_count = 0
810                        source.synapseServer.protocol.bad_packets = 0
811                else:
812                        source.packet_count = 0
813                        source.bad_packets = 0
814               
815                self.updateProfileSessionStatus()
816               
817                try:
818                        source.textEditDebugConsole.setText("")
819                except:
820                        pass
821       
822       
823        #####################################################################
824       
825        def resetSessionStartTime(self, source=None):
826               
827                self.session_start_timestamp = time.time()
828               
829               
830        #####################################################################
831       
832        def convert_seconds_to_datetime(self, duration):
833               
834                duration_hours = duration / (60 * 60)
835                duration_minutes = (duration - (duration_hours * (60 * 60))) / 60
836                duration_seconds = (duration - (duration_hours * (60 * 60)) - (duration_minutes * 60))
837               
838                duration_hours = '%i' % duration_hours
839                if (len(duration_hours) == 1):
840                        duration_hours = "0%s" % duration_hours
841               
842                duration_minutes = '%i' % duration_minutes
843                if (len(duration_minutes) == 1):
844                        duration_minutes = "0%s" % duration_minutes
845               
846                duration_seconds = '%i' % duration_seconds
847                if (len(duration_seconds) == 1):
848                        duration_seconds = "0%s" % duration_seconds
849               
850                datetime = '%s:%s:%s' % (duration_hours, duration_minutes, duration_seconds)
851               
852                return(datetime)
853
Note: See TracBrowser for help on using the repository browser.