Пример #1
0
 def parent(self, item):
     if not item.isValid():
         return QtCore.QModelIndex()
     item = item.internalPointer()
     parent_item = item.parent()
     if (parent_item == self.root_item):
         return QtCore.QModelIndex()
     index = self.createIndex(parent_item.row(), 0, parent_item)
     parent_item.index = index
     return index
Пример #2
0
 def parent(self, index):
     if not index.isValid():
         return QtCore.QModelIndex()
     tp, idx = index.internalPointer()
     if tp == 'group':
         return QtCore.QModelIndex()
     elif tp == 'cluster':
         # Get groupidx from clusteridx (=idx).
         groupidx = self.cluster_groups[idx]
         return self.createIndex(groupidx, 0, ('group', groupidx))
     return QtCore.QModelIndex()
Пример #3
0
class SimilarityMatrixTask(QtCore.QObject):
    correlationMatrixComputed = QtCore.pyqtSignal(np.ndarray, object,
        np.ndarray, np.ndarray, object)
    
    # def __init__(self, parent=None):
        # super(SimilarityMatrixTask, self).__init__(parent)
        
    def compute(self, features, clusters, 
            cluster_groups, masks, clusters_selected, target_next=None,
            similarity_measure=None):
        log.debug("Computing correlation for clusters {0:s}.".format(
            str(list(clusters_selected))))
        if len(clusters_selected) == 0:
            return {}
        
        correlations = compute_correlations(features, clusters, 
            masks, clusters_selected, similarity_measure=similarity_measure)
        return correlations
        
    def compute_done(self, features, clusters, 
            cluster_groups, masks, clusters_selected, target_next=None,
            similarity_measure=None, _result=None):
        correlations = _result
        self.correlationMatrixComputed.emit(np.array(clusters_selected),
            correlations, 
            get_array(clusters, copy=True), 
            get_array(cluster_groups, copy=True),
            target_next)
Пример #4
0
class OpenTask(QtCore.QObject):
    dataOpened = QtCore.pyqtSignal()
    dataSaved = QtCore.pyqtSignal()
    dataOpenFailed = QtCore.pyqtSignal(str)

    def open(self, loader, path):
        try:
            loader.close()
            loader.open(path)
            self.dataOpened.emit()
        except Exception as e:
            self.dataOpenFailed.emit(traceback.format_exc())

    def save(self, loader):
        loader.save()
        self.dataSaved.emit()
Пример #5
0
 def index(self, row, column, parent=None):
     if parent is None:
         parent = self.root_item.index
     if not self.hasIndex(row, column, parent):
         return QtCore.QModelIndex()
     if not parent.isValid():
         parent_item = self.root_item
     else:
         parent_item = parent.internalPointer()
     child_item = parent_item.child(row)
     if child_item:
         index = self.createIndex(row, column, child_item)
         child_item.index = index
         return index
     else:
         return QtCore.QModelIndex()
Пример #6
0
def start_delayed(fun, delay):
    _timer_master = QtCore.QTimer()
    _timer_master.setSingleShot(True)
    _timer_master.setInterval(int(delay * 1000))
    _timer_master.timeout.connect(wrap(fun))
    _timer_master.start()
    return _timer_master
class OutLog(QtCore.QObject):
    # We use signals so that this stream object can write from another thread
    # and the QTextEdit widget will be updated in the main thread through
    # a slot connected in the main window.
    writeRequested = QtCore.pyqtSignal(str)

    def write(self, m):
        self.writeRequested.emit(m)
Пример #8
0
def get_application():
    """Get the current QApplication, or create a new one."""
    app_created = False
    app = QtCore.QCoreApplication.instance()
    if app is None:
        app = QtCore.QCoreApplication(sys.argv)
        app_created = True
    return app, app_created
Пример #9
0
class Buffer(QtCore.QObject):
    accepted = QtCore.pyqtSignal(object)
    
    def __init__(self, parent=None, delay_timer=None, delay_buffer=None):
        """Create a new buffer.
        
        The user can request an item at any time. The buffer will respond
        to the requests only after some delay where no request happened.
        This allows users to handle time-consuming requests smoothly, when the
        latency is not critical.
        
          * delay_timer: time interval during two visits.
          * delay_buffer: minimum time interval between two accepted requests.
        
        """
        super(Buffer, self).__init__(parent)
        self.delay_timer = delay_timer
        self.delay_buffer = delay_buffer
        
    
    # Internal methods.
    # -----------------
    def _accept(self):
        # log.debug("Accept")
        self.accepted.emit(self._buffer.pop())
        self._last_accepted = time()
        self._buffer = []
    
    def _visit(self):
        delay = time() - self._last_request
        n = len(self._buffer)
        # log.debug("Visit {0:d} {1:.5f}".format(n, delay))
        # Only accept items that have been put after a sufficiently long
        # idle time.
        if ((n == 1 and (delay >= self.delay_buffer / 2)) or 
           ((n >= 2) and (delay >= self.delay_buffer))):
            self._accept()
    
    
    # Public methods.
    # ---------------
    def start(self):
        self._buffer = []
        self._last_request = 0
        self._last_accepted = 0
        
        self.timer = QtCore.QTimer(self)
        self.timer.setInterval(int(self.delay_timer * 1000))
        self.timer.timeout.connect(self._visit)
        self.timer.start()
        
    def stop(self):
        self.timer.stop()
        
    def request(self, item):
        self._buffer.append(item)
        n = len(self._buffer)
        self._last_request = time()
Пример #10
0
 def __init__(self, parent=None, data=None):
     """data is an OrderedDict"""
     self.parent_item = parent
     self.index = QtCore.QModelIndex()
     self.children = []
     # by default: root
     if data is None:
         data = OrderedDict(name='root')
     self.item_data = data
Пример #11
0
 def start(self):
     self._buffer = []
     self._last_request = 0
     self._last_accepted = 0
     
     self.timer = QtCore.QTimer(self)
     self.timer.setInterval(int(self.delay_timer * 1000))
     self.timer.timeout.connect(self._visit)
     self.timer.start()
Пример #12
0
 def rowCount(self, parent=None):
     if parent is None:
         parent = QtCore.QModelIndex()
     if parent.column() > 0:
         return 0
     if not parent.isValid():
         parent_item = self.root_item
     else:
         parent_item = parent.internalPointer()
     return parent_item.rowCount()
Пример #13
0
 def index(self, row, column, parent=None):
     if parent is None:
         parent = QtCore.QModelIndex()
     data = parent.internalPointer()
     if data is None:
         return self.createIndex(row, column, ('group', row))
     else:
         tp, idx = data
         assert tp == 'cluster'
         return self.createIndex(row, column, ('cluster', idx))
Пример #14
0
class ReclusterTask(QtCore.QObject):
    reclusterDone = QtCore.pyqtSignal(int, object, object, object, object)
    
    def recluster(self, exp, channel_group=0, clusters=None, wizard=None):
        spikes, clu = run_klustakwik(exp, channel_group=channel_group, 
                             clusters=clusters)
        return spikes, clu
        
    def recluster_done(self, exp, channel_group=0, clusters=None, wizard=None, _result=None):
        spikes, clu = _result
        self.reclusterDone.emit(channel_group, clusters, spikes, clu, wizard)
Пример #15
0
class SelectionTask(QtCore.QObject):
    selectionDone = QtCore.pyqtSignal(object, bool, int)

    def set_loader(self, loader):
        self.loader = loader

    def select(self, clusters, wizard, channel_group=0):
        self.loader.select(clusters=clusters)

    def select_done(self, clusters, wizard, channel_group=0, _result=None):
        self.selectionDone.emit(clusters, wizard, channel_group)
Пример #16
0
    class TestWindow(QtGui.QMainWindow):
        operatorStarted = QtCore.pyqtSignal(int)

        def __init__(self):
            super(TestWindow, self).__init__()
            self.setFocusPolicy(QtCore.Qt.WheelFocus)
            self.setMouseTracking(True)
            self.setWindowTitle("KlustaViewa")
            self.view = view_class(self, getfocus=False)
            self.view.set_data(**kwargs)
            self.setCentralWidget(self.view)
            self.move(100, 100)
            self.show()

            # Start "operator" asynchronously in the main thread.
            if operators:
                self.operator_list = operators
                self.operatorStarted.connect(self.operator)
                self._thread = threading.Thread(target=self._run_operator)
                self._thread.start()

        def _run_operator(self):
            for i in xrange(len(self.operator_list)):
                # Call asynchronously operation #i, after a given delay.
                if type(self.operator_list[i]) == tuple:
                    dt = self.operator_list[i][1]
                else:
                    # Default delay.
                    dt = USERPREF['test_operator_delay'] or .1
                time.sleep(dt)
                self.operatorStarted.emit(i)

        def operator(self, i):
            # Execute operation #i.
            if type(self.operator_list[i]) == tuple:
                fun = self.operator_list[i][0]
            else:
                fun = self.operator_list[i]
            fun(self)

        def keyPressEvent(self, e):
            super(TestWindow, self).keyPressEvent(e)
            self.view.keyPressEvent(e)
            if e.key() == QtCore.Qt.Key_Q:
                self.close()

        def keyReleaseEvent(self, e):
            super(TestWindow, self).keyReleaseEvent(e)
            self.view.keyReleaseEvent(e)

        def closeEvent(self, e):
            if operators:
                self._thread.join()
            return super(TestWindow, self).closeEvent(e)
Пример #17
0
 def __init__(self, group_sizes={}, cluster_groups=None):
     """
     
     Arguments:
     * group_sizes: a dictionary {groupidx: nclusters} that gives the number
       of clusters in each group.
     * cluster_groups: an object such that cluster_groups[clusteridx]
       returns the groupidx that contains that cluster.
     
     """
     QtCore.QAbstractItemModel.__init__(self)
     self.group_sizes = group_sizes
     self.cluster_groups = cluster_groups
     self.beginInsertRows(QtCore.QModelIndex(), 0, len(self.group_sizes))
     self.endInsertRows()
Пример #18
0
def test_buffer():
    app, app_created = get_application()

    test = BufferTest()

    # Launch the application.
    timer = QtCore.QTimer()
    timer.setSingleShot(True)
    timer.setInterval(100)
    timer.start()
    timer.timeout.connect(test.main)
    app.exec_()

    # print test.accepted_list
    assert test.accepted_list[0] == 0
    assert test.accepted_list[-1] == 13
Пример #19
0
    def create_feature_widget(self, coord=0):
        # coord => (channel, feature)
        self.projection = [(0, 0), (0, 1)]

        hbox = QtGui.QHBoxLayout()
        hbox.setSpacing(0)
        # HACK: pyside does not have this function
        if hasattr(hbox, 'setMargin'):
            hbox.setMargin(0)

        # channel selection
        comboBox = QtGui.QComboBox(self)
        comboBox.setEditable(True)
        comboBox.setMaximumWidth(100)
        comboBox.setInsertPolicy(QtGui.QComboBox.NoInsert)
        comboBox.addItems(["%d" % i for i in self.channels])
        comboBox.addItems(["Extra %d" % i for i in xrange(self.nextrafet)])
        comboBox.editTextChanged.connect(partial(self.select_channel, coord))
        # comboBox.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.channel_box[coord] = comboBox
        hbox.addWidget(comboBox)

        # create 3 buttons for selecting the feature
        widths = [30] * self.fetdim
        labels = ['PC%d' % i for i in xrange(1, self.fetdim + 1)]

        hbox.addSpacing(10)

        # ensure exclusivity of the group of buttons
        pushButtonGroup = QtGui.QButtonGroup(self)
        for i in xrange(len(labels)):
            # selecting feature i
            pushButton = QtGui.QPushButton(labels[i], self)
            pushButton.setCheckable(True)
            if coord == i:
                pushButton.setChecked(True)
            pushButton.setMaximumSize(QtCore.QSize(widths[i], 20))
            pushButton.clicked.connect(partial(self.select_feature, coord, i))
            pushButtonGroup.addButton(pushButton, i)
            self.feature_buttons[coord][i] = pushButton
            hbox.addWidget(pushButton)

        return hbox
Пример #20
0
 def create_widget(self):
     
     box = QtGui.QHBoxLayout()
     if hasattr(box, 'setMargin'):
         box.setContentsMargins(QtCore.QMargins(10, 2, 10, 2))
     
     # box.addSpacing(10)
     
     # coord => channel combo box
     self.channel_box = [None, None]
     # coord => (butA, butB, butC)
     self.feature_buttons = [[None] * self.fetdim, [None] * self.fetdim]
     
     # add feature widget
     self.feature_widget1 = self.create_feature_widget(0)
     box.addLayout(self.feature_widget1)
     
     box.addSpacing(10)
     
     # Switch button.
     # button = QtGui.QPushButton('Flip', self)
     button = QtGui.QPushButton(self)
     button.setIcon(get_icon('flip'))
     button.setMaximumWidth(40)
     button.clicked.connect(self.flip_projections_callback)
     box.addWidget(button)
     
     box.addSpacing(10)
     
     # add feature widget
     self.feature_widget2 = self.create_feature_widget(1)
     box.addLayout(self.feature_widget2)
     
     # box.addSpacing(10)
     
     self.setTabOrder(self.channel_box[0], self.channel_box[1])
     
     # self.setMaximumWidth(300)
     # self.setMaximumHeight(80)
     
     
     return box
Пример #21
0
class CorrelogramsTask(QtCore.QObject):
    correlogramsComputed = QtCore.pyqtSignal(np.ndarray, object, int, float,
                                             object)

    # def __init__(self, parent=None):
    # super(CorrelogramsTask, self).__init__(parent)

    def compute(self,
                spiketimes,
                clusters,
                clusters_to_update=None,
                clusters_selected=None,
                ncorrbins=None,
                corrbin=None,
                wizard=None):
        log.debug("Computing correlograms for clusters {0:s}.".format(
            str(list(clusters_to_update))))
        if len(clusters_to_update) == 0:
            return {}
        clusters_to_update = np.array(clusters_to_update, dtype=np.int32)
        correlograms = compute_correlograms(
            spiketimes,
            clusters,
            clusters_to_update=clusters_to_update,
            ncorrbins=ncorrbins,
            corrbin=corrbin)
        return correlograms

    def compute_done(self,
                     spiketimes,
                     clusters,
                     clusters_to_update=None,
                     clusters_selected=None,
                     ncorrbins=None,
                     corrbin=None,
                     wizard=None,
                     _result=None):
        correlograms = _result
        self.correlogramsComputed.emit(np.array(clusters_selected),
                                       correlograms, ncorrbins, corrbin,
                                       wizard)
Пример #22
0
class TestTasks(QtCore.QObject):
    squareDone = QtCore.pyqtSignal(int)

    # Constructor
    # -----------
    def __init__(self, parent=None):
        super(TestTasks, self).__init__(parent)

    # Square task
    # -----------
    def square(self, x):
        time.sleep(.1)
        return x * x

    def square_done(self, x, _result=None):
        self.squareDone.emit(_result)

    # Operation task
    # --------------
    def operation(self, x, y, coeff=1):
        time.sleep(.1)
        return coeff * (x + y)
Пример #23
0
class ViewDockWidget(QtGui.QDockWidget):
    closed = QtCore.pyqtSignal(object)
    def __init__(self, parent):
        super(ViewDockWidget, self).__init__(parent)
        self.setFocusPolicy(QtCore.Qt.WheelFocus)
    
    def closeEvent(self, e):
        self.closed.emit(self)
        super(ViewDockWidget, self).closeEvent(e)
    
    def keyPressEvent(self, e):
        super(ViewDockWidget, self).keyPressEvent(e)
        # Notify the main window of the key events when the dock widget
        # is floating.
        if self.isFloating():
            self.parent().keyPressEvent(e)
        
    def keyReleaseEvent(self, e):
        super(ViewDockWidget, self).keyReleaseEvent(e)
        # Notify the main window of the key events when the dock widget
        # is floating.
        if self.isFloating():
            self.parent().keyReleaseEvent(e)
Пример #24
0
 def minimumSizeHint(self):
     return QtCore.QSize(50, 24)
Пример #25
0
class ChannelViewModel(TreeModel):
    headers = ['Channel', 'Color']
    channelsMoved = QtCore.pyqtSignal(np.ndarray, int)

    def __init__(self, **kwargs):
        """Initialize the tree model.
        
        Arguments:
          * channels: a Nspikes long array with the channel index for each
            spike.
          * channels_info: an Info object with fields names, colors,
            groups_info.
        
        """
        super(ChannelViewModel, self).__init__(self.headers)
        self.background = {}
        self.load(**kwargs)

    # I/O methods
    # -----------
    def load(self,
             channel_colors=None,
             channel_groups=None,
             channel_names=None,
             group_colors=None,
             group_names=None,
             background={}):

        if group_names is None or channel_colors is None:
            return

        # Create the tree.
        # go through all groups
        for groupidx, groupname in group_names.iteritems():
            groupitem = self.add_group_node(groupidx=groupidx,
                                            name=groupname,
                                            color=select(
                                                group_colors, groupidx))

        # go through all channels
        for channelidx, color in channel_colors.iteritems():
            # add channel
            bgcolor = background.get(channelidx, None)
            channelitem = self.add_channel(name=channel_names[channelidx],
                                           channelidx=channelidx,
                                           color=color,
                                           bgcolor=None,
                                           parent=self.get_group(
                                               select(channel_groups,
                                                      channelidx)))

    # Data methods
    # ------------
    def headerData(self, section, orientation, role):
        if (orientation
                == QtCore.Qt.Horizontal) and (role == QtCore.Qt.DisplayRole):
            return self.headers[section]

    def data(self, index, role):
        item = index.internalPointer()

        col = index.column()
        # group item
        if type(item) == GroupItem:
            if col == 0:
                if role == QtCore.Qt.DisplayRole:
                    return str(item.name())
            # color
            elif col == self.columnCount() - 1:
                if role == QtCore.Qt.BackgroundRole:
                    if item.color() >= 0:
                        color = np.array(COLORMAP[item.color()]) * 255
                        return QtGui.QColor(*color)
                elif role == QtCore.Qt.DisplayRole:
                    return ""

        # channel item
        if type(item) == ChannelItem:
            # name
            if col == 0:
                if role == QtCore.Qt.DisplayRole:
                    return str(item.name())
                elif role == QtCore.Qt.BackgroundRole:
                    if item.bgcolor is None:
                        return
                    elif item.bgcolor == 'candidate':
                        color = np.array(COLORMAP[item.color()]) * 255
                        return QtGui.QColor(color[0], color[1], color[2], 90)
                    elif item.bgcolor == 'target':
                        # return QtGui.QColor(177, 177, 177, 255)
                        color = np.array(COLORMAP[item.color()]) * 255
                        return QtGui.QColor(color[0], color[1], color[2], 255)
                elif role == QtCore.Qt.ForegroundRole:
                    if item.bgcolor is None:
                        return QtGui.QColor(177, 177, 177, 255)
                    elif item.bgcolor == 'target':
                        return QtGui.QColor(0, 0, 0, 255)
            # color
            elif col == self.columnCount() - 1:
                if role == QtCore.Qt.BackgroundRole:
                    color = np.array(COLORMAP[item.color()]) * 255
                    return QtGui.QColor(*color)

        # default
        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            return item.data(col)

        if role == QtCore.Qt.ForegroundRole:
            return QtGui.QColor(177, 177, 177, 255)

    def setData(self, index, data, role=None):
        if role is None:
            role = QtCore.Qt.EditRole
        if index.isValid() and role == QtCore.Qt.EditRole:
            item = index.internalPointer()
            if index.column() == 0:
                item.item_data['name'] = data
            elif index.column() == 1:
                item.item_data['color'] = data
            self.dataChanged.emit(index, index)
            return True

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.ItemIsEnabled
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | \
               QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled

    def set_background(self, background=None):
        """Set the background of some channels. The argument is a dictionary
        channelidx ==> color index."""
        if background is not None:
            # Record the changed channels.
            self.background.update(background)
            # Get all channels to update.
            keys = self.background.keys()
            # Reset the keys
            if not background:
                self.background = {}
        # If background is None, use the previous one.
        else:
            background = self.background
            keys = self.background.keys()
        for channelidx in keys:
            bgcolor = background.get(channelidx, None)
            groupidx = self.get_groupidx(channelidx)
            # If the channel does not exist yet in the view, just discard it.
            if groupidx is None:
                continue
            group = self.get_group(groupidx)
            channel = self.get_channel(channelidx)
            index = self.index(channel.row(), 0, parent=group.index)
            index1 = self.index(channel.row(), 1, parent=group.index)
            if index.isValid():
                item = index.internalPointer()
                # bgcolor = True means using the same color
                # if bgcolor is True:
                # bgcolor = item.color()
                item.bgcolor = bgcolor
                self.dataChanged.emit(index, index1)

    # Action methods
    # --------------
    def add_group(self, name, color):
        """Add a group."""
        groupidx = max([group.groupidx() for group in self.get_groups()]) + 1
        # Add the group in the tree.
        groupitem = self.add_group_node(groupidx=groupidx,
                                        name=name,
                                        color=color)
        return groupitem

    def add_group_node(self, **kwargs):
        return self.add_node(item_class=GroupItem, **kwargs)

    def remove_group(self, group):
        """Remove an empty group. Raise an error if the group is not empty."""
        groupidx = group.groupidx()
        # check that the group is empty
        if self.get_channels_in_group(groupidx):
            raise ValueError("group %d is not empty, unable to delete it" % \
                    groupidx)
        groups = [g for g in self.get_groups() if g.groupidx() == groupidx]
        if groups:
            group = groups[0]
            self.remove_node(group)
        else:
            log.warn("Group %d does not exist0" % groupidx)

    def add_channel(self, parent=None, **kwargs):
        channel = self.add_node(item_class=ChannelItem,
                                parent=parent,
                                **kwargs)
        return channel

    def move_channels(self, sources, target):
        # Get the groupidx if the target is a group,
        if type(target) == GroupItem:
            groupidx = target.groupidx()
            target = None
        # else, if it is a channel, take the corresponding group.
        elif type(target) == ChannelItem:
            groupidx = self.get_groupidx(target.channelidx())
        else:
            return None

        # Move channels.
        target_group = self.get_group(groupidx)
        for node in sources:
            self._move_channel(node, target_group, target)

        self.update_group_sizes()

    def _move_channel(self, channel, parent_target, child_target=None):
        row = channel.row()
        parent_source = channel.parent()
        # Find the row where the channel needs to be inserted.
        if child_target is not None:
            child_target_row = child_target.row()
        else:
            child_target_row = parent_target.rowCount()
        # Begin moving the row.
        canmove = self.beginMoveRows(parent_source.index, row, row,
                                     parent_target.index, child_target_row)
        if canmove:
            # Create a new channel, clone of the old one.
            channel_new = ChannelItem(parent=parent_target,
                                      channelidx=channel.channelidx(),
                                      name=channel.name(),
                                      color=channel.color())
            # Create the index.
            channel_new.index = self.createIndex(child_target_row, 0,
                                                 channel_new)
            # Insert the new channel.
            parent_target.insertChild(channel_new, child_target_row)
            # Remove the old channel.
            if parent_target == parent_source:
                if child_target_row < row:
                    row += 1
                parent_source.removeChildAt(row)
            else:
                parent_source.removeChild(channel)
            self.endMoveRows()

    # Drag and drop for moving channels
    # ---------------------------------
    def drag(self, target, sources):
        # Get source ChannelItem nodes.
        source_items = [
            node for node in self.all_nodes()
            if (str(node) in sources and type(node) == ChannelItem)
        ]
        # Find the target group.
        if type(target) == GroupItem:
            groupidx = target.groupidx()
        # else, if it is a channel, take the corresponding group.
        elif type(target) == ChannelItem:
            groupidx = self.get_groupidx(target.channelidx())
        # Emit internal signal to let TreeView emit a public signal, and to
        # effectively move the channels.
        self.channelsMoved.emit(
            np.array([channel.channelidx() for channel in source_items]),
            groupidx)

    def rename_channel_group(self, group, name):
        self.setData(self.index(group.row(), 0), name)

    def rename_channel(self, channel, name):
        groupidx = self.get_groupidx(channel.channelidx())
        group = self.get_group(groupidx)
        self.setData(self.index(channel.row(), 0, parent=group.index), name)

    def change_group_color(self, group, color):
        self.setData(self.index(group.row(), 2), color)

    def change_channel_color(self, channel, color):
        groupidx = self.get_groupidx(channel.channelidx())
        group = self.get_group(groupidx)
        self.setData(self.index(channel.row(), 2, parent=group.index), color)

    # Getter methods
    # --------------
    def get_groups(self):
        return [group for group in self.get_descendants(self.root_item) \
            if (type(group) == GroupItem)]

    def get_group(self, groupidx):
        return [group for group in self.get_descendants(self.root_item) \
            if (type(group) == GroupItem) and \
                (group.groupidx() == groupidx)][0]

    def get_channels(self):
        return [channel for channel in self.get_descendants(self.root_item) \
          if (type(channel) == ChannelItem)]

    def get_channel(self, channelidx):
        l = [channel for channel in self.get_descendants(self.root_item) \
                  if (type(channel) == ChannelItem) and \
                        (channel.channelidx() == channelidx)]
        if l:
            return l[0]

    def get_channels_in_group(self, groupidx):
        group = self.get_group(groupidx)
        return [channel for channel in self.get_descendants(group) \
            if (type(channel) == ChannelItem)]

    def get_groupidx(self, channelidx):
        """Return the group index currently assigned to the specifyed channel
        index."""
        for group in self.get_groups():
            channelindices = [channel.channelidx() \
                            for channel in self.get_channels_in_group(group.groupidx())]
            if channelidx in channelindices:
                return group.groupidx()
        return None
Пример #26
0
def qInitResources():
    QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
Пример #27
0
def qCleanupResources():
    QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name,
                                   qt_resource_data)
Пример #28
0
def qInitResources():
    QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name,
                                 qt_resource_data)
Пример #29
0
def qCleanupResources():
    QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
class Loader(QtCore.QObject):
    progressReported = QtCore.pyqtSignal(int, int)
    saveProgressReported = QtCore.pyqtSignal(int, int)
    
    # Progress report
    # ---------------
    def report_progress(self, index, count):
        self.progressReported.emit(index, count)
        
    def report_progress_save(self, index, count):
        self.saveProgressReported.emit(index, count)
        
    
    # Initialization methods
    # ----------------------
    def __init__(self, parent=None, filename=None, userpref=None):
        """Initialize a Loader object for loading Klusters-formatted files.
        
        Arguments:
          * filename: the full path of any file belonging to the same
            dataset.
        
        """
        super(Loader, self).__init__()
        self.spikes_selected = None
        self.clusters_selected = None
        self.override_color = False
        
        if not userpref:
            # HACK: if no UserPref is given in argument to the loader,
            # use a mock dictionary returning None all the time.
            class MockDict(object):
                def __getitem__(self, name):
                    return None
            userpref = MockDict()
        self.userpref = userpref
        
        if filename:
            self.filename = filename
            self.open(self.filename)
    
    def open(self, filename=None):
        """Open everything."""
        pass
        
    def open_spikes(self):
        """Open just spike-related information."""
        
    def open_traces(self):
        """Open just trace information."""

    def open_aesthetic(self):
        """Open aesthetic visualization-related information."""
        
    
    # Input-Output methods
    # --------------------
    def read(self):
        pass
    
    def save(self):
        pass
    
    def close(self):
        pass
    
    
    # Access to the data: spikes
    # --------------------------
    def select(self, spikes=None, clusters=None):
        if clusters is not None:
            spikes = get_spikes_in_clusters(clusters, self.clusters)    
        self.spikes_selected = spikes
        self.clusters_selected = clusters

    def unselect(self):
        self.select(spikes=None, clusters=None)
        
    def get_clusters_selected(self):
        return self.clusters_selected
        
    def has_selection(self):
        return self.clusters_selected is not None and len(self.clusters_selected) > 0
        
    def get_clusters_unique(self):
        return self.clusters_unique
    
    def get_features(self, spikes=None, clusters=None):
        if clusters is not None:
            spikes = get_spikes_in_clusters(clusters, self.clusters)
        if spikes is None:
            spikes = self.spikes_selected
        return select(self.features, spikes)
    
    def get_features_background(self):
        return self.features
        
    def get_some_features(self, clusters=None):
        """Return the features for a subset of all spikes: a large number
        of spikes from any cluster, and a controlled subset of the selected 
        clusters."""
        if clusters is None:
            clusters = self.clusters_selected
        if clusters is not None:
            spikes_background = get_some_spikes(self.clusters,
                nspikes_max=self.userpref['features_nspikes_background_max'],)
            spikes_clusters = get_some_spikes_in_clusters(
                clusters,
                self.clusters,
                counter=self.counter,
                nspikes_max_expected=self.userpref[
                    'features_nspikes_selection_max'],
                nspikes_per_cluster_min=self.userpref[
                    'features_nspikes_per_cluster_min'])
            spikes = np.union1d(spikes_background, spikes_clusters)
        else:
            spikes = self.spikes_selected
        return select(self.features, spikes)
        
    def get_spiketimes(self, spikes=None, clusters=None):
        if clusters is not None:
            spikes = get_spikes_in_clusters(clusters, self.clusters)
        if spikes is None:
            spikes = self.spikes_selected
        spiketimes = getattr(self, 'spiketimes', getattr(self, 'spiketimes_res', None))
        return select(spiketimes, spikes)
    
    def get_clusters(self, spikes=None, clusters=None):
        if clusters is not None:
            spikes = get_spikes_in_clusters(clusters, self.clusters)
        if spikes is None:
            spikes = self.spikes_selected
        return select(self.clusters, spikes)
    
    def get_masks(self, spikes=None, full=None, clusters=None):
        if clusters is not None:
            spikes = get_spikes_in_clusters(clusters, self.clusters)
        if spikes is None:
            spikes = self.spikes_selected
        if not full:
            masks = self.masks
        else:
            masks = self.masks_full
        return select(masks, spikes)
    
    def get_waveforms(self, spikes=None, clusters=None):
        if spikes is not None:
            return select(self.waveforms, spikes)
        else:
            if clusters is None:
                clusters = self.clusters_selected
            if clusters is not None:
                spikes = get_some_spikes_in_clusters(clusters, self.clusters,
                    counter=self.counter,
                    nspikes_max_expected=self.userpref['waveforms_nspikes_max_expected'],
                    nspikes_per_cluster_min=self.userpref['waveforms_nspikes_per_cluster_min'])
            else:
                spikes = self.spikes_selected
        return select(self.waveforms, spikes)
    
    def get_dat(self):
        return self.dat
    
    def get_spikes(self, clusters=None):
        if clusters is None:
            clusters = self.clusters_selected
        return get_indices(self.get_clusters(clusters=clusters))
    
    def get_duration(self):
        return self.duration
    
    
    # Access to the data: clusters
    # ----------------------------
    def get_cluster_colors(self, clusters=None, can_override=True,
            ):
        if clusters is None:
            clusters = self.clusters_selected
        if can_override and self.override_color:
            group_colors = get_array(self.get_group_colors('all'))
            groups = get_array(self.get_cluster_groups('all'))
            colors = pd.Series(group_colors[groups], 
                index=self.get_clusters_unique())
        else:
            colors = self.cluster_colors
        return select(colors, clusters)
    
    def get_cluster_color(self, cluster):
        try:
            return select(self.cluster_colors, cluster)
        except IndexError:
            return 0
    
    def get_cluster_groups(self, clusters=None):
        if clusters is None:
            clusters = self.clusters_selected
        return select(self.cluster_groups, clusters)
    
    def get_group_colors(self, groups=None):
        return select(self.group_colors, groups)
    
    def get_group_names(self, groups=None):
        return select(self.group_names, groups)
    
    def get_cluster_sizes(self, clusters=None):
        if clusters is None:
            clusters = self.clusters_selected
        # counter = Counter(self.clusters)
        sizes = pd.Series(self.counter, dtype=np.int32)
        return select(sizes, clusters)
    
    # Access to the data: channels
    # ----------------------------
    def get_channel_colors(self, channels=None, can_override=True,
            ):
        if channels is None:
            channels = self.channels_selected
        if can_override and self.override_color:
            channel_group_colors = get_array(self.get_channel_group_colors('all'))
            channel_groups = get_array(self.get_channel_groups('all'))
            colors = pd.Series(channel_group_colors[channel_groups], 
                index=self.channels)
        else:
            colors = self.channel_colors
        return select(colors, channels)

    def get_channel_color(self, channel):
        try:
            return select(self.channel_colors, channel)
        except IndexError:
            return 0
            
    def get_channel_names(self, channels=None):
        return select(self.channel_names, channels)

    def get_channels_visible(self, channels=None):
        return select(self.channels_visible, channels)

    def get_channel_groups(self, channels=None):
        if channels is None:
            channels = self.channels_selected
        return select(self.channel_groups, channels)

    def get_channel_groups_visible(self, channel_groups=None):
        return select(self.channel_groups_visible, channel_groups)

    def get_channel_group_colors(self, channel_groups=None):
        return select(self.channel_group_colors, channel_groups)

    def get_channel_group_names(self, channel_groups=None):
        return select(self.channel_group_names, channel_groups)

    # Access to the data: channel traces
    # ----------------------------------
    def get_traces(self):
        return select(self.raw)
        
    def get_freq(self):
        return select(self.freq)
    
    # Access to the data: misc
    # ------------------------
    def get_probe(self):
        return self.probe
    
    def get_probe_geometry(self):
        return self.probe
    
    def get_new_clusters(self, n=1):
        return self.clusters.max() + np.arange(1, n + 1, dtype=np.int32)
    
    def get_next_cluster(self, cluster):
        cluster_groups = self.get_cluster_groups('all')
        group = get_array(self.get_cluster_groups(cluster))
        clusters = get_indices(cluster_groups)
        cluster_groups = get_array(cluster_groups)
        samegroup = (cluster_groups == group) & (clusters > cluster)
        i = np.nonzero(samegroup)[0]
        if len(i) > 0:
            return clusters[i[0]]
        else:
            return cluster
    
    def get_new_group(self):
        groups = get_indices(self.group_names).values
        if len(groups) > 0:
            return groups.max() + 1
        else:
            return 0
    
    def set_override_color(self, override_color):
        self.override_color = override_color
    
    
    # Control methods
    # ---------------
    def _update_data(self,):
        """Update internal variables."""
        clusters_array = get_array(self.clusters)
        self.clusters_unique = np.unique(clusters_array)
        self.nclusters = len(self.clusters_unique)
        bincount = np.bincount(clusters_array)
        self.counter = {key: bincount[key] for key in np.nonzero(bincount)[0]}
        
    # Set.
    def set_cluster(self, spikes, cluster):
        self.clusters.ix[spikes] = cluster
        self._update_data()
        
    def set_cluster_groups(self, clusters, group):
        self.cluster_groups.ix[clusters] = group
        
    def set_cluster_colors(self, clusters, color):
        self.cluster_colors.ix[clusters] = color
        
    def set_group_names(self, groups, name):
        self.group_names.ix[groups] = name
        
    def set_group_colors(self, groups, color):
        self.group_colors.ix[groups] = color
        
    # Add.
    def add_cluster(self, cluster, group, color):
        if cluster not in self.cluster_groups.index:
            self.cluster_groups = self.cluster_groups.append(
                pd.Series([group], index=[cluster])).sort_index()
        if cluster not in self.cluster_colors.index:
            self.cluster_colors = self.cluster_colors.append(
                pd.Series([color], index=[cluster])).sort_index()
        
    def add_clusters(self, clusters, groups, colors):
        for cluster, group, color in zip(clusters, groups, colors):
            self.add_cluster(cluster, group, color)

    def add_group(self, group, name, color):
        if group not in self.group_colors.index:
            self.group_colors = self.group_colors.append(
                pd.Series([color], index=[group])).sort_index()
        if group not in self.group_names.index:
            self.group_names = self.group_names.append(
                pd.Series([name], index=[group])).sort_index()
        
    # Remove.
    def remove_cluster(self, cluster):
        if np.any(np.in1d(cluster, self.clusters)):
            raise ValueError(("Cluster {0:d} is not empty and cannot "
            "be removed.").format(cluster))
        if cluster in self.cluster_groups.index:
            self.cluster_groups = self.cluster_groups.drop(cluster)
        if cluster in self.cluster_colors.index:
            self.cluster_colors = self.cluster_colors.drop(cluster)
            
    def remove_group(self, group):
        if np.any(np.in1d(group, self.cluster_groups)):
            raise ValueError(("Group {0:d} is not empty and cannot "
            "be removed.").format(group))
        if group in self.group_colors.index:
            self.group_colors = self.group_colors.drop(group)
        if group in self.group_names.index:
            self.group_names = self.group_names.drop(group)
    
    def remove_empty_clusters(self):
        clusters_all = self.cluster_groups.index
        clusters_in_data = self.clusters_unique
        clusters_empty = sorted(set(clusters_all) - set(clusters_in_data))
        if len(clusters_empty) > 0:
            debug("Removing empty clusters {0:s}.".
                format(str(clusters_empty)))
            for cluster in clusters_empty:
                self.remove_cluster(cluster)
        return clusters_empty
    
    # Cluster and group info.
    def update_cluster_info(self):
        cluster_info = {
            'color': self.cluster_colors,
            'group': self.cluster_groups,
        }
        self.cluster_info = pd.DataFrame(cluster_info, dtype=np.int32)
    
    def update_group_info(self):
        group_info = {
            'color': self.group_colors,
            'name': self.group_names,
        }
        self.group_info = pd.DataFrame(group_info)
    
    # Renumber.
    def renumber(self):
        self.clusters_renumbered, self.cluster_info_renumbered = \
            renumber_clusters(self.clusters, self.cluster_info)
Пример #31
0
class FeatureProjectionView(QtGui.QWidget):
    spikesHighlighted = QtCore.pyqtSignal(np.ndarray)
    spikesSelected = QtCore.pyqtSignal(np.ndarray)
    projectionChanged = QtCore.pyqtSignal(int, int, int)

    def __init__(self, parent, getfocus=None):
        super(FeatureProjectionView, self).__init__(parent)
        # Focus policy.
        if getfocus:
            self.setFocusPolicy(QtCore.Qt.WheelFocus)
        else:
            self.setFocusPolicy(QtCore.Qt.NoFocus)
        self.setWindowTitle('FeatureView')
        self.create_layout()
        self.show()

    def create_layout(self):
        self.projection_view = ProjectionView(self, getfocus=False)
        self.feature_view = FeatureView(self, getfocus=False)
        self.set_data()

        # Connect the FeatureView signal to the top-level widget signals.
        self.feature_view.spikesHighlighted.connect(self.spikesHighlighted)
        self.feature_view.spikesSelected.connect(self.spikesSelected)

        # Connect the bottom-level projectionChanged signals to the top-level
        # widget signals.
        self.feature_view.projectionChanged.connect(self.projectionChanged)
        self.projection_view.projectionChanged.connect(self.projectionChanged)

        # Interconnect the projectionChanged between the two bottom-level widgets.
        self.projection_view.projectionChanged.connect(
            self.projection_changed_projection_callback)
        self.feature_view.projectionChanged.connect(
            self.projection_changed_feature_callback)

        box = QtGui.QVBoxLayout()
        # HACK: pyside does not have this function
        if hasattr(box, 'setMargin'):
            box.setMargin(0)

        box.addWidget(self.projection_view)
        box.addWidget(self.feature_view)

        self.setLayout(box)

    def set_data(self, *args, **kwargs):
        fetdim = kwargs.get('fetdim', 3)
        nchannels = kwargs.get('nchannels', 1)
        nextrafet = kwargs.get('nextrafet', 0)
        self.projection_view.set_data(fetdim=fetdim,
                                      nchannels=nchannels,
                                      nextrafet=nextrafet)
        self.feature_view.set_data(*args, **kwargs)

    def projection_changed_projection_callback(self, *args):
        self.feature_view.set_projection(*args, do_emit=False)

    def projection_changed_feature_callback(self, *args):
        self.projection_view.set_projection(*args, do_emit=False)

    # FeatureView methods
    # -------------------
    def set_wizard_pair(self, *args, **kwargs):
        return self.feature_view.set_wizard_pair(*args, **kwargs)

    def highlight_spikes(self, *args, **kwargs):
        return self.feature_view.highlight_spikes(*args, **kwargs)

    def select_spikes(self, *args, **kwargs):
        return self.feature_view.select_spikes(*args, **kwargs)

    def toggle_mask(self, *args, **kwargs):
        return self.feature_view.toggle_mask(*args, **kwargs)

    # ProjectionView methods
    # ----------------------
    def select_feature(self, *args, **kwargs):
        return self.projection_view.select_feature(*args, **kwargs)

    def select_channel(self, *args, **kwargs):
        return self.projection_view.select_channel(*args, **kwargs)

    def get_projection(self, *args, **kwargs):
        return self.projection_view.get_projection(*args, **kwargs)

    def set_projection(self, coord, channel, feature, do_emit=True):
        if feature == -1:
            feature = self.feature_view.projection_manager.get_smart_feature(
                coord, channel)
        self.projection_view.set_projection(coord,
                                            channel,
                                            feature,
                                            do_emit=do_emit)
        self.feature_view.set_projection(coord,
                                         channel,
                                         feature,
                                         do_emit=do_emit)

    # Event methods
    # -------------
    def keyPressEvent(self, e):
        super(FeatureProjectionView, self).keyPressEvent(e)
        [
            view.keyPressEvent(e)
            for view in (self.feature_view, self.projection_view)
        ]

    def keyReleaseEvent(self, e):
        super(FeatureProjectionView, self).keyReleaseEvent(e)
        [
            view.keyReleaseEvent(e)
            for view in (self.feature_view, self.projection_view)
        ]
Пример #32
0
class ChannelView(QtGui.QTreeView):
    # Signals
    # -------
    # Selection.
    # The boolean indicates whether the selection has been initiated externally
    # or not (internally by clicking on items in the view).
    channelsSelected = QtCore.pyqtSignal(np.ndarray, bool)
    # groupsSelected = QtCore.pyqtSignal(np.ndarray)

    # Channel and group info.
    channelColorChanged = QtCore.pyqtSignal(int, int)
    groupColorChanged = QtCore.pyqtSignal(int, int)
    groupRenamed = QtCore.pyqtSignal(int, object)
    channelRenamed = QtCore.pyqtSignal(int, object)

    channelsMoved = QtCore.pyqtSignal(np.ndarray, int)
    groupRemoved = QtCore.pyqtSignal(int)
    groupAdded = QtCore.pyqtSignal(int, str, int)

    class ChannelDelegate(QtGui.QStyledItemDelegate):
        def paint(self, painter, option, index):
            """Disable the color column so that the color remains the same even
            when it is selected."""
            # deactivate all columns except the first one, so that selection
            # is only possible in the first column
            if index.column() >= 1:
                if option.state and QtGui.QStyle.State_Selected:
                    option.state = option.state and QtGui.QStyle.State_Off
            super(ChannelView.ChannelDelegate,
                  self).paint(painter, option, index)

    def __init__(self, parent, getfocus=None):
        super(ChannelView, self).__init__(parent)
        # Current item.
        self.current_item = None
        self.wizard = False
        self.channels_selected_previous = []

        # Focus policy.
        if getfocus:
            self.setFocusPolicy(QtCore.Qt.WheelFocus)
        else:
            self.setFocusPolicy(QtCore.Qt.NoFocus)

        self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setAllColumnsShowFocus(True)
        # self.setFirstColumnSpanned(0, QtCore.QModelIndex(), True)
        # select full rows
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
        self.setMaximumWidth(300)

        # self.setRootIsDecorated(False)
        self.setItemDelegate(self.ChannelDelegate())

        # Create menu. (hidden because it doesn't work - yet!)
        self.create_actions()
        self.create_context_menu()

        self.restore_geometry()

    # Data methods
    # ------------
    def set_data(self, **kwargs):

        # if channel_colors is None:
        if kwargs.get('channel_colors', None) is None:
            return

        self.model = ChannelViewModel(**kwargs)

        self.setModel(self.model)
        self.expandAll()

        # set spkcount column size
        self.header().resizeSection(1, 40)
        self.header().resizeSection(2, 60)
        # # set color column size
        # self.header().resizeSection(3, 40)

        # HACK: drag is triggered in the model, so connect it to move_channels
        # in this function
        self.model.channelsMoved.connect(self.move_channels)

    def clear(self):
        self.setModel(ChannelViewModel())

    # Public methods
    # --------------
    def select(self, channels, groups=None, wizard=False):
        """Select multiple channels from their indices."""
        self.wizard = wizard
        if channels is None:
            channels = []
        if groups is None:
            groups = []
        if isinstance(channels, (int, long, np.integer)):
            channels = [channels]
        if isinstance(groups, (int, long, np.integer)):
            groups = [groups]
        if len(channels) == len(groups) == 0:
            return
        # Convert to lists.
        channels = list(channels)
        groups = list(groups)
        selection_model = self.selectionModel()
        selection = QtGui.QItemSelection()
        # Select groups.
        for groupidx in groups:
            group = self.model.get_group(groupidx)
            if group is not None:
                selection.select(group.index, group.index)
        # Select channels.
        for channelidx in channels:
            channel = self.model.get_channel(channelidx)
            if channel is not None:
                selection.select(channel.index, channel.index)
        # Process selection.
        selection_model.select(
            selection, selection_model.Clear | selection_model.Current
            | selection_model.Select | selection_model.Rows)
        if len(channels) > 0:
            channel = self.model.get_channel(channels[-1])
            if channel is not None:
                # Set current index in the selection.
                selection_model.setCurrentIndex(
                    channel.index, QtGui.QItemSelectionModel.NoUpdate)
                # Scroll to that channel.
                self.scrollTo(channel.index)

    def unselect(self):
        self.selectionModel().clear()

    def move_channels(self, channels, groupidx):
        if not hasattr(channels, '__len__'):
            channels = [channels]
        if len(channels) == 0:
            return
        # Signal.
        log.debug("Moving channels {0:s} to group {1:d}.".format(
            str(channels), groupidx))
        self.channelsMoved.emit(np.array(channels), groupidx)

    def add_group(self, name, channels=[]):
        color = random_color()
        group = self.model.add_group(name, color)
        groupidx = group.groupidx()
        # Signal.
        log.debug("Adding group {0:s}.".format(name))
        self.groupAdded.emit(groupidx, name, color)
        # Move the selected channels to the new group.
        if channels:
            self.move_channels(channels, groupidx)
        self.expandAll()
        return groupidx

    def rename_channel_group(self, groupidx, name):
        self.model.rename_channel_group(self.model.get_group(groupidx), name)
        # Signal.
        log.debug("Rename group {0:d} to {1:s}.".format(groupidx, name))
        self.groupRenamed.emit(groupidx, name)

    def rename_channel(self, channelidx, name):
        self.model.rename_channel(self.model.get_channel(channelidx), name)
        # Signal.
        log.debug("Rename channel {0:d} to {1:s}.".format(channelidx, name))
        self.channelRenamed.emit(channelidx, name)

    def remove_group(self, groupidx):
        self.model.remove_group(self.model.get_group(groupidx))
        # Signal.
        log.debug("Removed group {0:d}.".format(groupidx))
        self.groupRemoved.emit(groupidx)

    def change_channel_color(self, channelidx, color):
        self.model.change_channel_color(self.model.get_channel(channelidx),
                                        color)
        # Signal.
        log.debug("Changed color of channel {0:d} to {1:d}.".format(
            channelidx, color))
        self.channelColorChanged.emit(channelidx, color)

    def change_group_color(self, groupidx, color):
        self.model.change_group_color(self.model.get_group(groupidx), color)
        # Signal.
        log.debug("Changed color of group {0:d} to {1:d}.".format(
            groupidx, color))
        self.groupColorChanged.emit(groupidx, color)

    def set_background(self, background=None):
        self.model.set_background(background)

    # Menu methods
    # ------------
    def create_color_dialog(self):
        self.color_dialog = QtGui.QColorDialog(self)
        self.color_dialog.setOptions(QtGui.QColorDialog.DontUseNativeDialog)
        for i in xrange(48):
            if i < len(COLORMAP):
                rgb = COLORMAP[i] * 255
            else:
                rgb = (255, 255, 255)
                # rgb = (1., 1., 1.)
            k = 6 * (np.mod(i, 8)) + i // 8
            self.color_dialog.setStandardColor(k, QtGui.qRgb(*rgb))

    def create_actions(self):

        self.change_color_action = QtGui.QAction("Change &color", self)
        self.change_color_action.triggered.connect(self.change_color_callback)

        self.add_group_action = QtGui.QAction("&Add group", self)
        self.add_group_action.triggered.connect(self.add_group_callback)

        self.rename_channel_group_action = QtGui.QAction("Re&name group", self)
        self.rename_channel_group_action.setShortcut("F2")
        self.rename_channel_group_action.triggered.connect(
            self.rename_channel_group_callback)

        self.rename_channel_action = QtGui.QAction("Rename c&hannel", self)
        self.rename_channel_action.setShortcut("F3")
        self.rename_channel_action.triggered.connect(
            self.rename_channel_callback)

        self.remove_group_action = QtGui.QAction("&Remove group", self)
        self.remove_group_action.triggered.connect(self.remove_group_callback)

        # Add actions to the widget.
        self.addAction(self.change_color_action)
        self.addAction(self.add_group_action)
        self.addAction(self.rename_channel_group_action)
        self.addAction(self.rename_channel_action)
        self.addAction(self.remove_group_action)

    def create_context_menu(self):
        self.create_color_dialog()

        self.context_menu = QtGui.QMenu(self)
        self.context_menu.addAction(self.change_color_action)
        self.context_menu.addSeparator()
        self.context_menu.addAction(self.add_group_action)
        self.context_menu.addAction(self.rename_channel_group_action)
        self.context_menu.addAction(self.rename_channel_action)
        self.context_menu.addAction(self.remove_group_action)

    def update_actions(self):
        channels = self.selected_channels()
        groups = self.selected_groups()

        if len(groups) > 0:
            self.rename_channel_group_action.setEnabled(True)
            # First three groups are not removable (noise and MUA and good).
            if 0 not in groups and 1 not in groups and 2 not in groups:
                self.remove_group_action.setEnabled(True)
            else:
                self.remove_group_action.setEnabled(False)
        else:
            self.rename_channel_group_action.setEnabled(False)
            self.remove_group_action.setEnabled(False)

        if len(channels) > 0:
            self.rename_channel_action.setEnabled(True)
        else:
            self.rename_channel_action.setEnabled(False)

    def contextMenuEvent(self, event):
        action = self.context_menu.exec_(self.mapToGlobal(event.pos()))

    def currentChanged(self, index, previous):
        self.current_item = index.internalPointer()

    # Callback
    # --------
    def change_color_callback(self, checked=None):
        item = self.current_item
        initial_color = item.color()
        if initial_color >= 0:
            initial_color = 255 * COLORMAP[initial_color]
            initial_color = QtGui.QColor(*initial_color)
            color = QtGui.QColorDialog.getColor(initial_color)
        else:
            color = QtGui.QColorDialog.getColor()
        # return if the user canceled
        if not color.isValid():
            return
        # get the RGB values of the chosen color
        rgb = np.array(color.getRgbF()[:3]).reshape((1, -1))
        # take the closest color in the palette
        color = np.argmin(np.abs(COLORMAP[1:, :] - rgb).sum(axis=1)) + 1
        # Change the color and emit the signal.
        if isinstance(item, ChannelItem):
            self.change_channel_color(item.channelidx(), color)
        elif isinstance(item, GroupItem):
            self.change_group_color(item.groupidx(), color)

    def add_group_callback(self, checked=None):
        text, ok = QtGui.QInputDialog.getText(self, "Group name",
                                              "Name group:",
                                              QtGui.QLineEdit.Normal,
                                              "New group")
        if ok:
            self.add_group(text, self.selected_channels())

    def remove_group_callback(self, checked=None):
        item = self.current_item
        if isinstance(item, GroupItem):
            self.remove_group(item.groupidx())

    def rename_channel_group_callback(self, checked=None):
        group = self.current_item
        if isinstance(group, GroupItem):
            groupidx = group.groupidx()
            name = group.name()
            text, ok = QtGui.QInputDialog.getText(self, "Group name",
                                                  "Rename group:",
                                                  QtGui.QLineEdit.Normal, name)
            if ok:
                # Rename the group.
                self.rename_channel_group(groupidx, text)

    def rename_channel_callback(self, checked=None):
        channel = self.current_item
        if isinstance(channel, ChannelItem):
            channelidx = channel.channelidx()
            name = channel.name()
            text, ok = QtGui.QInputDialog.getText(self, "Channel name",
                                                  "Rename channel:",
                                                  QtGui.QLineEdit.Normal, name)
            if ok:
                # Rename the group.
                self.rename_channel(channelidx, text)

    # Get methods
    # -----------
    def get_channel_indices(self):
        return [channel.channelidx() for channel in self.model.get_channels()]

    def get_group_indices(self):
        return [group.groupidx() for group in self.model.get_groups()]

    def get_channel_indices_in_group(self, groupidx):
        return [
            channel.channelidx()
            for channel in self.model.get_channels_in_group(groupidx)
        ]

    # Selection methods
    # -----------------
    def selectionChanged(self, selected, deselected):
        super(ChannelView, self).selectionChanged(selected, deselected)
        selected_channels = self.selected_channels()
        selected_groups = self.selected_groups()
        # All channels in selected groups minus selected channels.
        channels = [
            channel for group in selected_groups
            for channel in self.get_channel_indices_in_group(group)
            if channel not in selected_channels
        ]
        # Add selected channels not in selected groups.
        channels.extend([
            channel for channel in selected_channels
            if (channel not in channels
                and self.model.get_groupidx(channel) not in selected_groups)
        ])

        # Selected groups.
        group_indices = [
            self.model.get_group(groupidx) for groupidx in selected_groups
        ]

        # log.debug("Selected {0:d} channels.".format(len(channels)))
        # log.debug("Selected channels {0:s}.".format(str(channels)))
        self.channelsSelected.emit(np.array(channels, dtype=np.int32),
                                   self.wizard)

        # if group_indices:
        # self.scrollTo(group_indices[-1].index)
        if len(self.channels_selected_previous) <= 1:
            if len(channels) == 1:
                self.scrollTo(self.model.get_channel(channels[0]).index)
            elif len(group_indices) == 1:
                self.scrollTo(group_indices[0].index)

        self.wizard = False
        self.channels_selected_previous = channels
        self.update_actions()

    # Selected items
    # --------------
    def selected_items(self):
        """Return the list of selected channel indices."""
        return [(v.internalPointer()) \
                    for v in self.selectedIndexes() \
                        if v.column() == 0]

    def selected_channels(self):
        """Return the list of selected channel indices."""
        return [(v.internalPointer().channelidx()) \
                    for v in self.selectedIndexes() \
                        if v.column() == 0 and \
                           type(v.internalPointer()) == ChannelItem]

    def selected_groups(self):
        """Return the list of selected groups."""
        return [(v.internalPointer().groupidx()) \
                    for v in self.selectedIndexes() \
                        if v.column() == 0 and \
                           type(v.internalPointer()) == GroupItem]

    # Event methods
    # -------------
    def keyPressEvent(self, e):
        key = e.key()
        modif = e.modifiers()
        ctrl = modif & QtCore.Qt.ControlModifier
        shift = modif & QtCore.Qt.ShiftModifier
        alt = modif & QtCore.Qt.AltModifier
        if (ctrl and key == QtCore.Qt.Key_A):
            self.select(self.get_channel_indices())
        else:
            return super(ChannelView, self).keyPressEvent(e)

    # Save and restore geometry
    # -------------------------
    def save_geometry(self):
        SETTINGS['channel_widget.geometry'] = encode_bytearray(
            self.saveGeometry())
        SETTINGS['channel_widget.header'] = encode_bytearray(
            self.header().saveState())

    def restore_geometry(self):
        g = SETTINGS['channel_widget.geometry']
        h = SETTINGS['channel_widget.header']
        if g:
            self.restoreGeometry(decode_bytearray(g))
        if h:
            self.header().restoreState(decode_bytearray(h))

    def closeEvent(self, e):
        # Save the window geometry when closing the software.
        self.save_geometry()
        return super(ChannelView, self).closeEvent(e)