diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 0d20b64..2ed465b --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ *.pyc +/eeg_data/* +/build/* +.vscode/settings.json + +.idea diff --git a/COPYING b/COPYING old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 25b3bf3..44c8994 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ specification to ease the process of analysing signals with FieldTrip. Installation ============ +Installing on macOS: +```brew install pygobject3 gtk+3``` + Just run ```python setup.py install``` to install the module on your system. Testing diff --git a/TODO.md b/TODO.md old mode 100644 new mode 100755 diff --git a/conda-environment.yml b/conda-environment.yml new file mode 100644 index 0000000..d6d45a9 --- /dev/null +++ b/conda-environment.yml @@ -0,0 +1,14 @@ +name: python2-emotiv +dependencies: + - python=2.7 + - numpy + - pycrypto + - scipy + - wheel + - nose + - cryptography + - pyOpenSSL + - python-dateutil + - pip: + - pylsl + - pyusb \ No newline at end of file diff --git a/doc/lsusb.txt b/doc/lsusb.txt old mode 100644 new mode 100755 diff --git a/doc/sc_console.png b/doc/sc_console.png old mode 100644 new mode 100755 diff --git a/emotiv/__init__.py b/emotiv/__init__.py old mode 100644 new mode 100755 diff --git a/emotiv/analysis.py b/emotiv/analysis.py old mode 100644 new mode 100755 diff --git a/emotiv/epoc.py b/emotiv/epoc.py old mode 100644 new mode 100755 index 121aa9a..051e0ee --- a/emotiv/epoc.py +++ b/emotiv/epoc.py @@ -35,405 +35,446 @@ class EPOCError(Exception): - """Base class for exceptions in this module.""" - pass + """Base class for exceptions in this module.""" + pass class EPOCTurnedOffError(EPOCError): - """Exception raised when Emotiv EPOC is not turned on.""" - pass + """Exception raised when Emotiv EPOC is not turned on.""" + pass class EPOCDeviceNodeNotFoundError(EPOCError): - """Exception raised when /dev/emotiv_epoc is missing.""" - pass + """Exception raised when /dev/emotiv_epoc is missing.""" + pass class EPOCUSBError(EPOCError): - """Exception raised when error occurs during I/O operations.""" - pass + """Exception raised when error occurs during I/O operations.""" + pass class EPOCNotPluggedError(EPOCError): - """Exception raised when EPOC dongle cannot be detected.""" - pass + """Exception raised when EPOC dongle cannot be detected.""" + pass class EPOCPermissionError(EPOCError): - """Exception raised when EPOC dongle cannot be opened for I/O.""" - pass + """Exception raised when EPOC dongle cannot be opened for I/O.""" + pass class EPOC(object): - """Class for accessing Emotiv EPOC headset devices.""" - - # Device descriptions for USB - INTERFACE_DESC = "Emotiv RAW DATA" - MANUFACTURER_PREFIX = "Emotiv Systems" - - # Channel names - channels = ["F3", "FC5", "AF3", "F7", "T7", "P7", "O1", - "O2", "P8", "T8", "F8", "AF4", "FC6", "F4"] - - # Sampling rate: 128Hz (Internal: 2048Hz) - sampling_rate = 128 - - # Vertical resolution interval (0.51uV) - vres = 0.51 - - # Battery levels - # github.com/openyou/emokit/blob/master/doc/emotiv_protocol.asciidoc - battery_levels = {247: 99, 246: 97, 245: 93, 244: 89, 243: 85, - 242: 82, 241: 77, 240: 72, 239: 66, 238: 62, - 237: 55, 236: 46, 235: 32, 234: 20, 233: 12, - 232: 6, 231: 4, 230: 3, 229: 2, 228: 1, - 227: 1, 226: 1, - } - # 100% for bit values between 248-255 - battery_levels.update(dict([(k, 100) for k in range(248, 256)])) - # 0% for bit values between 128-225 - battery_levels.update(dict([(k, 0) for k in range(128, 226)])) - - # Define a contact quality ordering - # github.com/openyou/emokit/blob/master/doc/emotiv_protocol.asciidoc - - # For counter values between 0-15 - cq_order = ["F3", "FC5", "AF3", "F7", "T7", "P7", "O1", - "O2", "P8", "T8", "F8", "AF4", "FC6", "F4", - "F8", "AF4"] - - # 16-63 is currently unknown - cq_order.extend([None, ] * 48) - - # Now the first 16 values repeat once more and ends with 'FC6' - cq_order.extend(cq_order[:16]) - cq_order.append("FC6") - - # Finally pattern 77-80 repeats until 127 - cq_order.extend(cq_order[-4:] * 12) - - # emokit-style bit indexes to use with utils.get_level() - bit_indexes = { - 'F3': [10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7], - 'FC5': [28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 8, 9], - 'AF3': [46, 47, 32, 33, 34, 35, 36, 37, 38, 39, 24, 25, 26, 27], - 'F7': [48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45], - 'T7': [66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61, 62, 63], - 'P7': [84, 85, 86, 87, 72, 73, 74, 75, 76, 77, 78, 79, 64, 65], - 'O1': [102, 103, 88, 89, 90, 91, 92, 93, 94, 95, 80, 81, 82, 83], - 'O2': [140, 141, 142, 143, 128, 129, 130, 131, 132, 133, 134, 135, 120, 121], - 'P8': [158, 159, 144, 145, 146, 147, 148, 149, 150, 151, 136, 137, 138, 139], - 'T8': [160, 161, 162, 163, 164, 165, 166, 167, 152, 153, 154, 155, 156, 157], - 'F8': [178, 179, 180, 181, 182, 183, 168, 169, 170, 171, 172, 173, 174, 175], - 'AF4': [196, 197, 198, 199, 184, 185, 186, 187, 188, 189, 190, 191, 176, 177], - 'FC6': [214, 215, 200, 201, 202, 203, 204, 205, 206, 207, 192, 193, 194, 195], - 'F4': [216, 217, 218, 219, 220, 221, 222, 223, 208, 209, 210, 211, 212, 213], - 'QU': [99,100,101,102,103,104,105,106,107,108,109,110,111,112], - } - - def __init__(self, method="libusb", serial_number=None, enable_gyro=True): - self.vendor_id = None - self.product_id = None - self.decryption = None - self.decryption_key = None - self.headset_on = False - self.headset_type = "research" # Default to research, detect later - self.enable_gyro = enable_gyro - self.battery = 0 - self.counter = 0 - self.gyroX = 0 - self.gyroY = 0 - - # Access method can be direct/libusb/dummy (Default: libusb) - # If dummy is given the class behaves as a random signal generator - self.method = method - - # One may like to specify the dongle with its serial - self.serial_number = serial_number - - # libusb device and endpoint - self.device = None - self.endpoint = None - - # By default acquire from all channels - self.channel_mask = self.channels - - # Dict for storing contact qualities - self.quality = { - "F3": 0, "FC5": 0, "AF3": 0, "F7": 0, - "T7": 0, "P7": 0, "O1": 0, "O2": 0, - "P8": 0, "T8": 0, "F8": 0, "AF4": 0, - "FC6": 0, "F4": 0, - } - - # Update __dict__ with convenience attributes for channels - self.__dict__.update(dict((v, k) for k, v in enumerate(self.channels))) - - # Enumerate the bus to find EPOC devices - self.enumerate() - - def _is_epoc(self, device): - """Custom match function for libusb.""" - try: - manu = usb.util.get_string(device, device.iManufacturer) - except usb.core.USBError, usb_exception: - # If the udev rule is installed, we shouldn't get an exception - # for Emotiv device. - print usb_exception - return False - else: - if manu and manu.startswith(self.MANUFACTURER_PREFIX): - print manu - return True - # FIXME: This may not be necessary at all Found a dongle, check for interface class 3 - for interf in device.get_active_configuration(): - if_str = usb.util.get_string(device, interf.iInterface) - if if_str == self.INTERFACE_DESC: - return True - - def set_channel_mask(self, channel_mask): - """Set channels from which to acquire.""" - self.channel_mask = channel_mask - - def enumerate(self): - """Traverse through USB bus and enumerate EPOC devices.""" - if self.method == "dummy": - self.endpoint = open("/dev/urandom") - self.get_sample = self.__get_sample_dummy - return - - devices = usb.core.find(find_all=True, custom_match=self._is_epoc) - - if not devices: - raise EPOCNotPluggedError("Emotiv EPOC not found.") - - for dev in devices: - serial = usb.util.get_string(dev, dev.iSerialNumber) - if self.serial_number and self.serial_number != serial: - # If a special S/N is given, look for it. - continue - - # Record some attributes - self.serial_number = serial - self.vendor_id = "%x" % dev.idVendor - self.product_id = "%x" % dev.idProduct - - if self.product_id == "0001": - print "Consumer headset detected." - self.headset_type = "consumer" - - if self.method == "libusb": - # Last interface is the one we need - for interface in dev.get_active_configuration(): - if dev.is_kernel_driver_active(interface.bInterfaceNumber): - # Detach kernel drivers and claim through libusb - dev.detach_kernel_driver(interface.bInterfaceNumber) - usb.util.claim_interface(dev, interface.bInterfaceNumber) - - self.device = dev - self.endpoint = usb.util.find_descriptor( - interface, bEndpointAddress=usb.ENDPOINT_IN | 2) - elif self.method == "direct": - if os.path.exists("/dev/emotiv_epoc"): - self.endpoint = open("/dev/emotiv_epoc") - else: - raise EPOCDeviceNodeNotFoundError( - "/dev/emotiv_epoc doesn't exist.") - - # Return the first Emotiv headset by default - break - - self.setup_encryption() - # Attempt to see whether the headset is turned on - try: - self.endpoint.read(32, 100) - except usb.USBError as ue: - if ue.errno == 110: - self.headset_on = False - print "Setup is OK but make sure that headset is turned on." - else: - self.headset_on = True - - def setup_encryption(self): - """Generate the encryption key and setup Crypto module. - The key is based on the serial number of the device and the - information whether it is a research or consumer device. - """ - if self.headset_type == "research": - self.decryption_key = ''.join([self.serial_number[15], '\x00', - self.serial_number[14], '\x54', - self.serial_number[13], '\x10', - self.serial_number[12], '\x42', - self.serial_number[15], '\x00', - self.serial_number[14], '\x48', - self.serial_number[13], '\x00', - self.serial_number[12], '\x50']) - elif self.headset_type == "consumer": - self.decryption_key = ''.join([self.serial_number[15], '\x00', - self.serial_number[14], '\x48', - self.serial_number[13], '\x00', - self.serial_number[12], '\x54', - self.serial_number[15], '\x10', - self.serial_number[14], '\x42', - self.serial_number[13], '\x00', - self.serial_number[12], '\x50']) - - self._cipher = AES.new(self.decryption_key) - - def set_external_decryption(self): - """Use another process for concurrent decryption.""" - self.decryption = Process(target=decryptionProcess, - args=[self._cipher, - self.input_queue, - self.output_queue, False]) - self.decryption.daemon = True - self.decryption.start() - - def __get_sample_dummy(self): - """Read random dummy samples.""" - raw_data = self.endpoint.read(32) - return [utils.get_level(raw_data, self.bit_indexes[n]) for n in self.channel_mask] - - def get_sample(self): - """Returns an array of EEG samples.""" - try: - raw_data = self._cipher.decrypt(self.endpoint.read(32)) - # Parse counter - ctr = ord(raw_data[0]) - # Update gyro's if requested - if self.enable_gyro: - self.gyroX = ((ord(raw_data[29]) << 4) | (ord(raw_data[31]) >> 4)) - self.gyroY = ((ord(raw_data[30]) << 4) | (ord(raw_data[31]) & 0x0F)) - if ctr < 128: - self.counter = ctr - # Contact qualities - if self.cq_order[ctr]: - self.quality[self.cq_order[ctr]] = utils.get_level(raw_data, self.bit_indexes["QU"]) / 540.0 - # Finally EEG data - return [0.51 * utils.get_level(raw_data, self.bit_indexes[n]) for n in self.channel_mask] - else: - # Set a synthetic counter for this special packet: 128 - self.counter = 128 - # Parse battery level - self.battery = self.battery_levels[ctr] - return [] - except usb.USBError as usb_exception: - if usb_exception.errno == 110: - self.headset_on = False - raise EPOCTurnedOffError( - "Make sure that headset is turned on") - else: - raise EPOCUSBError("USB I/O error with errno = %d" % - usb_exception.errno) - - def acquire_data(self, duration): - """Acquire data from the EPOC headset.""" - - total_samples = duration * self.sampling_rate - _buffer = np.ndarray((total_samples, len(self.channel_mask) + 1), - dtype=np.uint16) - ctr = 0 - while ctr < total_samples: - # Fetch new data - data = self.get_sample() - if data: - # Prepend sequence numbers - _buffer[ctr] = np.insert(np.array(data), 0, self.counter) - ctr += 1 - - return _buffer - - def acquire_data_fast(self, duration, stop_callback=None, stop_callback_param=None): - """A more optimized method to acquire data from the EPOC headset without calling get_sample().""" - - def get_level(raw_data, bits): - """Returns signal level from raw_data frame.""" - level = 0 - for i in range(13, -1, -1): - level <<= 1 - b, o = (bits[i] / 8) + 1, bits[i] % 8 - level |= (ord(raw_data[b]) >> o) & 1 - # Return level in uV (microVolts) - return level - - bit_indexes = [self.bit_indexes[n] for n in self.channel_mask] - # Packet idx to keep track of losses - idx = [] - total_samples = duration * self.sampling_rate - - # Pre-allocated array - _buffer = np.ndarray((total_samples, len(self.channel_mask)), dtype=np.float64) - - # Acquire in one read, this should be more robust against drops - raw_data = self._cipher.decrypt(self.endpoint.read(32 * (total_samples + duration + 1), timeout=(duration+1)*1000)) - - if stop_callback and stop_callback_param: - stop_callback(stop_callback_param) - - # Split data back into 32-byte chunks, skipping 1st packet - split_data = [raw_data[i:i + 32] for i in range(32, len(raw_data), 32)] - - # Loop ctr - c = 0 - for block in split_data: - if c == total_samples: - break - # Parse counter - ctr = ord(block[0]) - # Skip battery - if ctr < 128: - idx.append(ctr) - _buffer[c] = [0.51 * get_level(block, bi) for bi in bit_indexes] - c += 1 - # Update qualities as well - if self.cq_order[ctr] is not None: - self.quality[self.cq_order[ctr]] = utils.get_level(block, self.bit_indexes["QU"]) / 540.0 - else: - # Parse battery level - self.battery = self.battery_levels[ctr] - - return idx, _buffer - - def get_quality(self, electrode): - "Return contact quality for the specified electrode.""" - return self.quality.get(electrode, None) - - def disconnect(self): - """Release the claimed interface.""" - if self.method == "libusb": - for interf in self.device.get_active_configuration(): - usb.util.release_interface( - self.device, interf.bInterfaceNumber) - else: - self.endpoint.close() + """Class for accessing Emotiv EPOC headset devices.""" + + # Device descriptions for USB + INTERFACE_DESC = "Emotiv RAW DATA" + MANUFACTURER_PREFIX = "Emotiv Systems" + + # Channel names + channels = ["F3", "FC5", "AF3", "F7", "T7", "P7", "O1", + "O2", "P8", "T8", "F8", "AF4", "FC6", "F4"] + + # Sampling rate: 128Hz (Internal: 2048Hz) + sampling_rate = 128 + + # Vertical resolution interval (0.51uV) + vres = 0.51 + + # Battery levels + # github.com/openyou/emokit/blob/master/doc/emotiv_protocol.asciidoc + battery_levels = {247: 99, 246: 97, 245: 93, 244: 89, 243: 85, + 242: 82, 241: 77, 240: 72, 239: 66, 238: 62, + 237: 55, 236: 46, 235: 32, 234: 20, 233: 12, + 232: 6, 231: 4, 230: 3, 229: 2, 228: 1, + 227: 1, 226: 1, + } + # 100% for bit values between 248-255 + battery_levels.update(dict([(k, 100) for k in range(248, 256)])) + # 0% for bit values between 128-225 + battery_levels.update(dict([(k, 0) for k in range(128, 226)])) + + # Define a contact quality ordering + # github.com/openyou/emokit/blob/master/doc/emotiv_protocol.asciidoc + + # For counter values between 0-15 + cq_order = ["F3", "FC5", "AF3", "F7", "T7", "P7", "O1", + "O2", "P8", "T8", "F8", "AF4", "FC6", "F4", + "F8", "AF4"] + + # 16-63 is currently unknown + cq_order.extend([None, ] * 48) + + # Now the first 16 values repeat once more and ends with 'FC6' + cq_order.extend(cq_order[:16]) + cq_order.append("FC6") + + # Finally pattern 77-80 repeats until 127 + cq_order.extend(cq_order[-4:] * 12) + + # emokit-style bit indexes to use with utils.get_level() + bit_indexes = { + 'F3': [10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7], + 'FC5': [28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 8, 9], + 'AF3': [46, 47, 32, 33, 34, 35, 36, 37, 38, 39, 24, 25, 26, 27], + 'F7': [48, 49, 50, 51, 52, 53, 54, 55, 40, 41, 42, 43, 44, 45], + 'T7': [66, 67, 68, 69, 70, 71, 56, 57, 58, 59, 60, 61, 62, 63], + 'P7': [84, 85, 86, 87, 72, 73, 74, 75, 76, 77, 78, 79, 64, 65], + 'O1': [102, 103, 88, 89, 90, 91, 92, 93, 94, 95, 80, 81, 82, 83], + 'O2': [140, 141, 142, 143, 128, 129, 130, 131, 132, 133, 134, 135, 120, 121], + 'P8': [158, 159, 144, 145, 146, 147, 148, 149, 150, 151, 136, 137, 138, 139], + 'T8': [160, 161, 162, 163, 164, 165, 166, 167, 152, 153, 154, 155, 156, 157], + 'F8': [178, 179, 180, 181, 182, 183, 168, 169, 170, 171, 172, 173, 174, 175], + 'AF4': [196, 197, 198, 199, 184, 185, 186, 187, 188, 189, 190, 191, 176, 177], + 'FC6': [214, 215, 200, 201, 202, 203, 204, 205, 206, 207, 192, 193, 194, 195], + 'F4': [216, 217, 218, 219, 220, 221, 222, 223, 208, 209, 210, 211, 212, 213], + 'QU': [99,100,101,102,103,104,105,106,107,108,109,110,111,112], + } + + def __init__(self, method="libusb", serial_number=None, enable_gyro=True): + self.vendor_id = None + self.product_id = None + self.decryption = None + self.decryption_key = None + self.headset_on = False + self.headset_type = "research" # Default to research, detect later + self.enable_gyro = enable_gyro + self.battery = 0 + self.counter = 0 + self.gyroX = 0 + self.gyroY = 0 + + # Access method can be direct/libusb/dummy (Default: libusb) + # If dummy is given the class behaves as a random signal generator + self.method = method + + # One may like to specify the dongle with its serial + self.serial_number = serial_number + + # libusb device and endpoint + self.device = None + self.endpoint = None + + # By default acquire from all channels + self.channel_mask = self.channels + + # Dict for storing contact qualities + self.quality = { + "F3": 0, "FC5": 0, "AF3": 0, "F7": 0, + "T7": 0, "P7": 0, "O1": 0, "O2": 0, + "P8": 0, "T8": 0, "F8": 0, "AF4": 0, + "FC6": 0, "F4": 0, + } + + # Update __dict__ with convenience attributes for channels + self.__dict__.update(dict((v, k) for k, v in enumerate(self.channels))) + + # Enumerate the bus to find EPOC devices + self.enumerate() + + def _is_epoc(self, device): + """Custom match function for libusb.""" + try: + manu = usb.util.get_string(device, device.iManufacturer) + except usb.core.USBError, usb_exception: + # If the udev rule is installed, we shouldn't get an exception + # for Emotiv device. + print usb_exception + return False + else: + if manu and manu.startswith(self.MANUFACTURER_PREFIX): + print manu + return True + # FIXME: This may not be necessary at all Found a dongle, check for interface class 3 + for interf in device.get_active_configuration(): + if_str = usb.util.get_string(device, interf.iInterface) + if if_str == self.INTERFACE_DESC: + return True + + def set_channel_mask(self, channel_mask): + """Set channels from which to acquire.""" + self.channel_mask = channel_mask + + def enumerate(self): + """Traverse through USB bus and enumerate EPOC devices.""" + if self.method == "dummy": + self.endpoint = open("/dev/urandom") + self.get_sample = self.__get_sample_dummy + return + + devices = usb.core.find(find_all=True, custom_match=self._is_epoc) + + if not devices: + raise EPOCNotPluggedError("Emotiv EPOC not found.") + + for dev in devices: + serial = usb.util.get_string(dev, dev.iSerialNumber) + if self.serial_number and self.serial_number != serial: + # If a special S/N is given, look for it. + continue + + # Record some attributes + self.serial_number = serial + self.vendor_id = "%x" % dev.idVendor + self.product_id = "%x" % dev.idProduct + + if self.product_id == "0001": + print "Consumer headset detected." + self.headset_type = "consumer" + + if self.method == "libusb": + # Last interface is the one we need + for interface in dev.get_active_configuration(): + if dev.is_kernel_driver_active(interface.bInterfaceNumber): + # Detach kernel drivers and claim through libusb + dev.detach_kernel_driver(interface.bInterfaceNumber) + usb.util.claim_interface(dev, interface.bInterfaceNumber) + + self.device = dev + self.endpoint = usb.util.find_descriptor( + interface, bEndpointAddress=usb.ENDPOINT_IN | 2) + elif self.method == "direct": + if os.path.exists("/dev/emotiv_epoc"): + self.endpoint = open("/dev/emotiv_epoc") + else: + raise EPOCDeviceNodeNotFoundError( + "/dev/emotiv_epoc doesn't exist.") + + # Return the first Emotiv headset by default + break + + self.setup_encryption() + # Attempt to see whether the headset is turned on + try: + self.endpoint.read(32, 100) + except usb.USBError as ue: + if ue.errno == 110: + self.headset_on = False + print "Setup is OK but make sure that headset is turned on." + else: + self.headset_on = True + + def setup_encryption(self): + """Generate the encryption key and setup Crypto module. + The key is based on the serial number of the device and the + information whether it is a research or consumer device. + """ + if self.headset_type == "research": + self.decryption_key = ''.join([self.serial_number[15], '\x00', + self.serial_number[14], '\x54', + self.serial_number[13], '\x10', + self.serial_number[12], '\x42', + self.serial_number[15], '\x00', + self.serial_number[14], '\x48', + self.serial_number[13], '\x00', + self.serial_number[12], '\x50']) + elif self.headset_type == "consumer": + self.decryption_key = ''.join([self.serial_number[15], '\x00', + self.serial_number[14], '\x48', + self.serial_number[13], '\x00', + self.serial_number[12], '\x54', + self.serial_number[15], '\x10', + self.serial_number[14], '\x42', + self.serial_number[13], '\x00', + self.serial_number[12], '\x50']) + + self._cipher = AES.new(self.decryption_key) + + def set_external_decryption(self): + """Use another process for concurrent decryption.""" + self.decryption = Process(target=decryptionProcess, + args=[self._cipher, + self.input_queue, + self.output_queue, False]) + self.decryption.daemon = True + self.decryption.start() + + def __get_sample_dummy(self): + """Read random dummy samples.""" + raw_data = self.endpoint.read(32) + return [utils.get_level(raw_data, self.bit_indexes[n]) for n in self.channel_mask] + + def get_sample(self): + """Returns an array of EEG samples.""" + # Called by LabStreamingLayer (LSL) Script, the "acquire_data(self, duration)" function + try: + raw_data = self._cipher.decrypt(self.endpoint.read(32)) + # Parse counter + ctr = ord(raw_data[0]) + # Update gyro's if requested + if self.enable_gyro: + self.gyroX = ((ord(raw_data[29]) << 4) | (ord(raw_data[31]) >> 4)) + self.gyroY = ((ord(raw_data[30]) << 4) | (ord(raw_data[31]) & 0x0F)) + if ctr < 128: + self.counter = ctr + # Contact qualities + if self.cq_order[ctr]: + self.quality[self.cq_order[ctr]] = utils.get_level(raw_data, self.bit_indexes["QU"]) / 540.0 + # Finally EEG data + return [0.51 * utils.get_level(raw_data, self.bit_indexes[n]) for n in self.channel_mask] + else: + # Set a synthetic counter for this special packet: 128 + self.counter = 128 + # Parse battery level + self.battery = self.battery_levels[ctr] + return [] + except usb.USBError as usb_exception: + if usb_exception.errno == 110: + self.headset_on = False + raise EPOCTurnedOffError( + "Make sure that headset is turned on") + else: + raise EPOCUSBError("USB I/O error with errno = %d" % + usb_exception.errno) + + def acquire_data(self, duration, sample_callback=None): + """Acquire data from the EPOC headset.""" + # Compute the total_samples to acquire from the provided duration + total_samples = duration * self.sampling_rate + # Pre-allocate the buffer to hold the data. + # TODO: why is this allocated with (total_samples, len(self.channel_mask) + 1) when the regular acquire_data_fast(...) only allocates with (total_samples, len(self.channel_mask))? + # The answer is because we prepend each output sample in the buffer with the self.counter value. + _buffer = np.ndarray( (total_samples, len( self.channel_mask ) + 1), dtype=np.uint16 ) + ctr = 0 + while ctr < total_samples: + # Fetch new data using the regular (get_sample) + data = self.get_sample() + if data: + # Send the callback + if sample_callback: + sample_callback( data ) + # Prepend sequence numbers + _buffer[ctr] = np.insert( np.array( data ), 0, self.counter ) + ctr += 1 + + return _buffer + + + def acquire_data_tracking_dropped(self, duration, sample_callback=None): + """Acquire data from the EPOC headset while also monitoring dropped packets.""" + # Compute the total_samples to acquire from the provided duration + total_samples = duration * self.sampling_rate + # Pre-allocate the buffer to hold the data. + _buffer = np.ndarray( (total_samples, len( self.channel_mask ) + 1), dtype=np.uint16 ) + # TODO: Drop the samples purposefully until the ctr is 0. + prev_ctr = self.counter + curr_data_acq_ctr = 0 + while curr_data_acq_ctr < total_samples: + # Fetch new data using the regular (get_sample) + data = self.get_sample() + ctr = self.counter + if data: + # data.shape: (1, numChannels) + if (prev_ctr + 1) % 129 != ctr: + print "Dropped packets between %d and %d" % (prev_ctr, ctr) + # Send the callback + if sample_callback: + sample_callback( data ) + # Prepend sequence numbers + _buffer[curr_data_acq_ctr] = np.insert( np.array( data ), 0, self.counter ) + # This insert statement simply prepends the self.counter scalar onto the front of the data array, making _buffer[curr_data_acq_ctr].shape: (1, (numChannels+1)) + curr_data_acq_ctr += 1 + else: + if (prev_ctr + 1) % 129 != ctr: + print "Dropped packets between %d and %d" % (prev_ctr, ctr) + print "Empty packet at %d" % (ctr) + + prev_ctr = ctr + + return _buffer + + def acquire_data_fast(self, duration, stop_callback=None, stop_callback_param=None): + """A more optimized method to acquire data from the EPOC headset without calling get_sample().""" + + def get_level(raw_data, bits): + """Returns signal level from raw_data frame.""" + level = 0 + for i in range(13, -1, -1): + level <<= 1 + b, o = (bits[i] / 8) + 1, bits[i] % 8 + level |= (ord(raw_data[b]) >> o) & 1 + # Return level in uV (microVolts) + return level + + bit_indexes = [self.bit_indexes[n] for n in self.channel_mask] + # Packet idx to keep track of losses + idx = [] + # Compute the total_samples to acquire from the provided duration + total_samples = duration * self.sampling_rate + + # Pre-allocated array + _buffer = np.ndarray((total_samples, len(self.channel_mask)), dtype=np.float64) + + # Acquire in one read, this should be more robust against drops + raw_data = self._cipher.decrypt(self.endpoint.read(32 * (total_samples + duration + 1), timeout=(duration+1)*1000)) + + if stop_callback and stop_callback_param: + stop_callback(stop_callback_param) + + # Split data back into 32-byte chunks, skipping 1st packet + split_data = [raw_data[i:i + 32] for i in range(32, len(raw_data), 32)] + + # Loop ctr + c = 0 + for block in split_data: + if c == total_samples: + break + # Parse counter + ctr = ord(block[0]) + # Skip battery + if ctr < 128: + idx.append(ctr) + _buffer[c] = [0.51 * get_level(block, bi) for bi in bit_indexes] + c += 1 + # Update qualities as well + if self.cq_order[ctr] is not None: + self.quality[self.cq_order[ctr]] = utils.get_level(block, self.bit_indexes["QU"]) / 540.0 + else: + # Parse battery level + self.battery = self.battery_levels[ctr] + + return idx, _buffer + + def get_quality(self, electrode): + "Return contact quality for the specified electrode.""" + return self.quality.get(electrode, None) + + def disconnect(self): + """Release the claimed interface.""" + if self.method == "libusb": + for interf in self.device.get_active_configuration(): + usb.util.release_interface( + self.device, interf.bInterfaceNumber) + else: + self.endpoint.close() def main(): - """Test function for EPOC class.""" - e = EPOC() - - while 1: - try: - data = e.get_sample() - # data is [] for each battery packet, e.g. ctr > 127 - if data: - # Clear screen - print("\x1b[2J\x1b[H") - header = "Emotiv Data Packet [%3d/128] [Loss: N/A] [Battery: %2d(%%)]" % ( - e.counter, e.battery) - print "%s\n%s" % (header, '-'*len(header)) - - print "%10s: %5d" % ("Gyro(x)", e.gyroX) - print "%10s: %5d" % ("Gyro(y)", e.gyroY) - - for i,channel in enumerate(e.channel_mask): - print "%10s: %.2f %20s: %.2f" % (channel, data[i], "Quality", e.quality[channel]) - except EPOCTurnedOffError, ete: - print ete - except KeyboardInterrupt, ki: - e.disconnect() - return 0 + """Test function for EPOC class.""" + e = EPOC() + + while 1: + try: + data = e.get_sample() + # data is [] for each battery packet, e.g. ctr > 127 + if data: + # Clear screen + print("\x1b[2J\x1b[H") + header = "Emotiv Data Packet [%3d/128] [Loss: N/A] [Battery: %2d(%%)]" % ( + e.counter, e.battery) + print "%s\n%s" % (header, '-'*len(header)) + + print "%10s: %5d" % ("Gyro(x)", e.gyroX) + print "%10s: %5d" % ("Gyro(y)", e.gyroY) + + for i,channel in enumerate(e.channel_mask): + print "%10s: %.2f %20s: %.2f" % (channel, data[i], "Quality", e.quality[channel]) + except EPOCTurnedOffError, ete: + print ete + except KeyboardInterrupt, ki: + e.disconnect() + return 0 if __name__ == "__main__": - import sys - sys.exit(main()) + import sys + sys.exit(main()) diff --git a/emotiv/toMatlab.sh b/emotiv/toMatlab.sh new file mode 100755 index 0000000..e669c26 --- /dev/null +++ b/emotiv/toMatlab.sh @@ -0,0 +1,3 @@ +from utils import save_as_matlab + +save_as_matlab() diff --git a/emotiv/utils.py b/emotiv/utils.py old mode 100644 new mode 100755 index 062d063..adb9aaf --- a/emotiv/utils.py +++ b/emotiv/utils.py @@ -44,8 +44,9 @@ def get_level(raw_data, bits): def save_as_matlab(_buffer, channel_mask, folder=None, prefix=None, filename=None, metadata=None): """Save as matlab data with optional metadata.""" nr_samples = _buffer[:, 0].size + nr_channels = _buffer[0, :].size trial = np.zeros((1,), dtype=np.object) - trial[0] = _buffer[:, 1:].astype(np.float64).T + trial[0] = _buffer[:, :].astype(np.float64).T trial_time = np.zeros((1,), dtype=np.object) trial_time[0] = np.array(range(nr_samples)) / 128.0 @@ -58,6 +59,7 @@ def save_as_matlab(_buffer, channel_mask, folder=None, prefix=None, filename=Non matlab_data = {} matlab_data["data"] = fieldtrip_data + matlab_data["numberOfChannels"] = nr_channels # Inject metadata if any if metadata: @@ -65,7 +67,8 @@ def save_as_matlab(_buffer, channel_mask, folder=None, prefix=None, filename=Non matlab_data[key] = value # Put time of recording - date_info = time.strftime("%d-%m-%Y_%H-%M-%S") + ## TODO: This seems to be the only time that the current time is got for the file. Since the file is saved to .mat format after logging completes, we have no clue what the initial recording start time was. + date_info = time.strftime("%d-%m-%Y_%H-%M-%S") # TODO: Include milliseconds? matlab_data["date"] = date_info if not filename: @@ -80,3 +83,4 @@ def save_as_matlab(_buffer, channel_mask, folder=None, prefix=None, filename=Non filename = os.path.join(folder, filename) savemat(filename, matlab_data, oned_as='row') + diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..e69de29 diff --git a/examples/pho-multi-record.py b/examples/pho-multi-record.py new file mode 100755 index 0000000..6a12e27 --- /dev/null +++ b/examples/pho-multi-record.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim:set et ts=4 sw=4: +# +## Copyright (C) 2019 Pho Hale + +import os +import sys +import thread + +import numpy as np + +try: + from emotiv import epoc, utils +except ImportError: + sys.path.insert(0, "..") + from emotiv import epoc, utils + +def headsetRead(headset): + idx, data = headset.acquire_data_fast(9) + print "Battery: %d %%" % headset.battery + print "Contact qualities" + print headset.quality + metadata = {"quality": headset.quality,"battery": headset.battery} + utils.save_as_matlab(data, headset.channel_mask, folder="../eeg_data", metadata=metadata) + +def input_thread(a_list): + raw_input() + a_list.append(True) + +def dataAcquisitionLoop(headset): + a_list = [] + thread.start_new_thread(input_thread, (a_list,)) + while not a_list: + headsetRead(headset) + + +def main(): + print "Running Data Aquisition: Press any key to terminate at the end of the block." + + channels = None + try: + channels = sys.argv[1].split(",") + except: + pass + + # Setup headset + headset = epoc.EPOC(enable_gyro=False) + if channels: + headset.set_channel_mask(channels) + + # Acquire + dataAcquisitionLoop(headset) + #print "Data Acquisition Terminated." + try: + headset.disconnect() + except e: + print e + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/pho-server-record.py b/examples/pho-server-record.py new file mode 100644 index 0000000..65e002f --- /dev/null +++ b/examples/pho-server-record.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim:set et ts=4 sw=4: +# +## Copyright (C) 2019 Pho Hale + +import os +import sys +import thread + +import numpy as np + +import pylsl +import time + +import socket +import json + +UDP_IP_ADDRESS = "10.0.0.90" +UDP_PORT_NO = 6789 +Message = "Hello, Server" +PacketSendDuration = 9 #Specifies how long between signal quality packets and saving to .mat file. In seconds. +ShouldTrackDroppedPackets = True + +try: + from emotiv import epoc, utils +except ImportError: + sys.path.insert(0, "..") + from emotiv import epoc, utils + + +def callback_single_sample_complete(outlet, sampleData): + #print("Items processed: {}. Running result: {}.".format(i, result)) + outlet.push_sample(pylsl.vectori(sampleData), pylsl.local_clock()) + + +def headsetRead(headset, single_sample_read_callback): + #idx, data = headset.acquire_data_fast(9) + if ShouldTrackDroppedPackets: + data = headset.acquire_data_tracking_dropped(PacketSendDuration, sample_callback=single_sample_read_callback) + else: + data = headset.acquire_data(PacketSendDuration, sample_callback=single_sample_read_callback) + + print "Battery: %d %%" % headset.battery + print "Contact qualities" + print headset.quality + metadata = {"quality": headset.quality,"battery": headset.battery} + return (data, metadata) + +def input_thread(a_list): + raw_input() + a_list.append(True) + + +def save_as_mat_thread(data, channel_mask, metadata): + utils.save_as_matlab(data, channel_mask, folder="../eeg_data", metadata=metadata) + +# def dataAcquisitionLoop(headset, outlets, clientSock): +def dataAcquisitionLoop(headset, outlets): + a_list = [] + thread.start_new_thread(input_thread, (a_list,)) + # Build the callback function as a lambda function + callback_single_single_sample_complete_with_outlet = lambda d: callback_single_sample_complete(outlets['data'], d) + while not a_list: + try: + data, metadata = headsetRead(headset, callback_single_single_sample_complete_with_outlet) + # Push the complete metadata on write + metadataOutputVector = [] + # print("battery type:", type(float(metadata["battery"]))) + metadataOutputVector.append( float( metadata["battery"] ) ) + # print("quality type:", type(metadata["quality"])) + + metadataOutputVector.extend( [metadata["quality"][v] for v in headset.channel_mask] ) + # for i,channel in enumerate(e.channel_mask): + # print "%10s: %.2f %20s: %.2f" % (channel, data[i], "Quality", e.quality[channel]) + outlets['metadata'].push_sample( pylsl.vectori( metadataOutputVector ), pylsl.local_clock() ) + # Send to the UDP server + # stringMetadata = json.dumps(metadata) + # clientSock.sendto(stringMetadata, (UDP_IP_ADDRESS, UDP_PORT_NO)) + # Perform the writing in a separate thread. + thread.start_new_thread( save_as_mat_thread, (data, headset.channel_mask, metadata) ) + + except epoc.EPOCTurnedOffError: + print("Headset has been disconnected! Trying to reconnect ...") + + except KeyboardInterrupt, ki: + # Handles keyboard interrupts + try: + headset.disconnect() + except e: + print e + return 0 + + +def setupLabStreamingLayer(headset): + # (self, name='untitled', type='', channel_count=1, nominal_srate=IRREGULAR_RATE, channel_format=cf_float32, source_id='', handle=None) + dataStreamInfo = pylsl.stream_info('Emotiv EEG', 'EEG', len(headset.channel_mask), headset.sampling_rate, pylsl.cf_float32, str(headset.serial_number)) + dataStreamInfo_desc = dataStreamInfo.desc() + dataStreamInfo_desc.append_child_value("manufacturer", "Emotiv") + + # The metadataStream consists of the channel qualities and the battery level + metadataStreamInfo = pylsl.stream_info('Emotiv EEG Meta', '', (len(headset.channel_mask) + 1), (1.0 / PacketSendDuration), pylsl.cf_float32, str(headset.serial_number)) + metadataStreamInfo_desc = metadataStreamInfo.desc() + metadataStreamInfo_desc.append_child_value("manufacturer", "Emotiv") + + channels = dataStreamInfo_desc.append_child("channels") + channelsMeta = metadataStreamInfo_desc.append_child("channels") + + channelsMeta.append_child("channel").append_child_value("label", "battery").append_child_value("unit","PercentCharge").append_child_value("type","") + for ch in headset.channel_mask: + channels.append_child("channel").append_child_value("label", ch).append_child_value("unit","microvolts").append_child_value("type","EEG") + channelsMeta.append_child("channel").append_child_value("label", ch).append_child_value("unit","EmotivQualityUnits").append_child_value("type","") + + # Outgoing buffer size = 360 seconds, transmission chunk size = 32 samples + # stream_outlet(info, chunk_size=0, max_buffered=360): + # outlets['data'] = pylsl.stream_outlet(dataStreamInfo, 1, 32) + # outlets['metadata'] = pylsl.stream_outlet(metadataStreamInfo, 1, 32) + # return outlets + return {"data": pylsl.stream_outlet(dataStreamInfo, 1, 32), "metadata": pylsl.stream_outlet(metadataStreamInfo, 1, 32)} + +def main(): + print "Running Data Aquisition: Press any key to terminate at the end of the block." + + channels = None + try: + channels = sys.argv[1].split(",") + except: + pass + + try: + # Setup headset + headset = epoc.EPOC(enable_gyro=False) + if channels: + headset.set_channel_mask(channels) + except ValueError as e: + if str(e) == 'The device has no langid': + print("!! ERROR: The USB Dongle doesn't appear to be connected.") + print("\t Please connect it and then try again!") + print("\t!!! Exiting!") + exit(1) + else: + print("Other Value Error: ", e) + print("\t!!! Exiting!") + raise + + # Setup UDP connection if possible + #clientSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + #clientSock.sendto(Message, (UDP_IP_ADDRESS, UDP_PORT_NO)) + + # Setup LabStreamingLayer connection + outlets = setupLabStreamingLayer(headset) + + # Acquire + # dataAcquisitionLoop(headset, outlets, clientSock) + dataAcquisitionLoop(headset, outlets) + #print "Data Acquisition Terminated." + try: + headset.disconnect() + except e: + print e + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/record-data.py b/examples/record-data.py old mode 100755 new mode 100644 index 4cd881d..579f2a3 --- a/examples/record-data.py +++ b/examples/record-data.py @@ -33,7 +33,7 @@ def main(): try: duration = int(sys.argv[1]) except: - print "Usage: %s [ch1,ch2,..,chN]" % sys.argv[0] + print "Usage: %s [ch1,ch2,..,chN]" % sys.argv[0] return 1 channels = None diff --git a/lsl/liblsl32.so b/lsl/liblsl32.so new file mode 100644 index 0000000..1a8d0c9 Binary files /dev/null and b/lsl/liblsl32.so differ diff --git a/lsl/liblsl32.so.1.13.0 b/lsl/liblsl32.so.1.13.0 new file mode 100644 index 0000000..1a8d0c9 Binary files /dev/null and b/lsl/liblsl32.so.1.13.0 differ diff --git a/lsl/lsl-pyemotiv.py b/lsl/lsl-pyemotiv.py old mode 100644 new mode 100755 index ef34a6f..b015b9e --- a/lsl/lsl-pyemotiv.py +++ b/lsl/lsl-pyemotiv.py @@ -4,39 +4,44 @@ import time try: - from emotiv import epoc + from emotiv import epoc except ImportError: - sys.path.insert(0, "..") - from emotiv import epoc + sys.path.insert(0, "..") + from emotiv import epoc if __name__ == '__main__': - headset = epoc.EPOC() - print "Found headset with serial number: ", headset.serial_number + headset = epoc.EPOC() + print "Found headset with serial number: ", headset.serial_number - info = pylsl.stream_info('Emotiv EEG', 'EEG', len(headset.channel_mask), - headset.sampling_rate, - pylsl.cf_int16, - str(headset.serial_number)) + # info = pylsl.stream_info('Emotiv EEG', 'EEG', len(headset.channel_mask), + # headset.sampling_rate, + # pylsl.cf_int16, + # str(headset.serial_number)) - info_desc = info.desc() - info_desc.append_child_value("manufacturer", "Emotiv") - channels = info_desc.append_child("channels") + info = pylsl.stream_info('Emotiv EEG', 'EEG', len(headset.channel_mask), + headset.sampling_rate, + pylsl.cf_float32, + str(headset.serial_number)) - for ch in headset.channel_mask: - channels.append_child("channel").append_child_value("label", ch).append_child_value("unit","microvolts").append_child_value("type","EEG") + info_desc = info.desc() + info_desc.append_child_value("manufacturer", "Emotiv") + channels = info_desc.append_child("channels") - # Outgoing buffer size = 360 seconds, transmission chunk size = 32 samples - outlet = pylsl.stream_outlet(info, 1, 32) + for ch in headset.channel_mask: + channels.append_child("channel").append_child_value("label", ch).append_child_value("unit","microvolts").append_child_value("type","EEG") - while True: - try: - s = headset.get_sample() - except epoc.EPOCTurnedOffError, e: - print "Headset is turned off, waiting..." - time.sleep(0.02) - else: - if s: - outlet.push_sample(pylsl.vectori(s), pylsl.local_clock()) + # Outgoing buffer size = 360 seconds, transmission chunk size = 32 samples + outlet = pylsl.stream_outlet(info, 1, 32) - headset.disconnect() + while True: + try: + s = headset.get_sample() + except epoc.EPOCTurnedOffError, e: + print "Headset is turned off, waiting..." + time.sleep(0.02) + else: + if s: + outlet.push_sample(pylsl.vectori(s), pylsl.local_clock()) + + headset.disconnect() diff --git a/lsl/pylsl.py b/lsl/pylsl.py deleted file mode 100644 index e16a2bb..0000000 --- a/lsl/pylsl.py +++ /dev/null @@ -1,10 +0,0 @@ -import os,sys,inspect - -try: - binary_path = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile(inspect.currentframe()))[0],"binaries-python" + str(sys.version_info[0]) + "." + str(sys.version_info[1])))) - if binary_path not in sys.path: - sys.path.append(binary_path) -except: - raise Exception("The pylsl module has not been compiled for your Python version.") - -from liblsl import * diff --git a/matlab/batch_eeg.m b/matlab/batch_eeg.m old mode 100644 new mode 100755 diff --git a/matlab/batch_eeg2.m b/matlab/batch_eeg2.m old mode 100644 new mode 100755 diff --git a/matlab/burg_classify.m b/matlab/burg_classify.m old mode 100644 new mode 100755 diff --git a/matlab/burg_plots_dataset.m b/matlab/burg_plots_dataset.m old mode 100644 new mode 100755 diff --git a/matlab/plot_all_channels.m b/matlab/plot_all_channels.m old mode 100644 new mode 100755 diff --git a/matlab/time_average_fft_animated.m b/matlab/time_average_fft_animated.m old mode 100644 new mode 100755 diff --git a/pho-requirements.txt b/pho-requirements.txt new file mode 100644 index 0000000..4b4787f --- /dev/null +++ b/pho-requirements.txt @@ -0,0 +1,35 @@ +## This file contains requirements that I'm certain are needed because I installed them manually. + +# pi@pho-pi:~/repo/python-emotiv $ which python +# /usr/bin/python + +# pi@pho-pi:~/repo/python-emotiv $ python -V +# Python 2.7.9 + +# pi@pho-pi:~/repo/python-emotiv $ which pip +# /usr/bin/pip +# pi@pho-pi:~/repo/python-emotiv $ pip -V +# pip 1.5.6 from /usr/lib/python2.7/dist-packages (python 2.7) + +python -m pip install pynput +# pi@pho-pi:~/repo/python-emotiv $ python -m pip install pynput +# Downloading/unpacking pynput +# Downloading pynput-1.4.2-py2.py3-none-any.whl (73kB): 73kB downloaded +# Downloading/unpacking python-xlib>=0.17 (from pynput) +# Downloading python_xlib-0.25-py2.py3-none-any.whl (165kB): 165kB downloaded +# Downloading/unpacking enum34 (from pynput) +# Downloading enum34-1.1.6-py2-none-any.whl +# Requirement already satisfied (use --upgrade to upgrade): six in /usr/lib/python2.7/dist-packages (from pynput) +# Installing collected packages: pynput, python-xlib, enum34 +# Successfully installed pynput python-xlib enum34 +# Cleaning up... + + + +python -m pip install --upgrade six + + +# File "/usr/local/lib/python2.7/dist-packages/Xlib/support/unix_connect.py", line 76, in get_display +# raise error.DisplayNameError(display) +# Xlib.error.DisplayNameError: Bad display name "" +# Will occur over SSH because X server isn't running \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..25dc2f0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,55 @@ +Pillow==2.6.1 +PyYAML==3.11 +Pygments==2.0.1 +Pyste==0.9.10 +RPi.GPIO==0.6.3 +RTIMULib==7.2.1 +argparse==1.2.1 +catkin-pkg==0.2.10 +catkin-pkg-modules==0.4.12 +cffi==0.8.6 +chardet==2.3.0 +colorama==0.3.2 +coverage==3.7.1 +cryptography==0.6.1 +decorator==3.4.0 +docutils==0.12 +ecdsa==0.11 +empy==3.3.2 +html5lib==0.999 +mock==1.0.1 +ndg-httpsclient==0.3.2 +netifaces==0.10.4 +nose==1.3.4 +numpy==1.8.2 +paramiko==1.15.1 +pigpio==1.30 +ply==3.4 +pyOpenSSL==0.14 +pyasn1==0.1.7 +pycparser==2.10 +pycrypto==2.6.1 +pygobject==3.14.0 +pylsl==1.12.2 +pyparsing==2.0.3 +python-dateutil==2.2 +python-emotiv==0.1 +pyusb==1.0.2 +pyxdg==0.25 +requests==2.4.3 +roman==2.0.0 +rosdep==0.11.4 +rosdistro==0.4.4 +rosdistro-modules==0.7.4 +rosinstall==0.7.7 +rosinstall-generator==0.1.11 +rospkg==1.0.38 +rospkg-modules==1.1.9 +scipy==0.14.0 +sense-hat==2.2.0 +six==1.8.0 +urllib3==1.9.1 +vcstools==0.1.38 +wheel==0.24.0 +wsgiref==0.1.2 +wstool==0.1.12 diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 diff --git a/test/packet_drop.py b/test/packet_drop.py old mode 100644 new mode 100755 diff --git a/udev/99-emotiv-epoc.rules b/udev/99-emotiv-epoc.rules old mode 100644 new mode 100755 diff --git a/utils/epoc-replug.py b/utils/epoc-replug.py old mode 100644 new mode 100755 diff --git a/utils/pisarenko.py b/utils/pisarenko.py old mode 100644 new mode 100755 diff --git a/utils/ssvep-frequencies-diff.py b/utils/ssvep-frequencies-diff.py old mode 100644 new mode 100755 diff --git a/utils/ssvep-frequencies.py b/utils/ssvep-frequencies.py old mode 100644 new mode 100755