Source code for tridesclous.gui.peaklists

from .myqt import QT
import pyqtgraph as pg

import numpy as np

from .. import labelcodes
from .base import WidgetBase
from .baselist import ClusterBaseList

from .tools import ParamDialog, open_dialog_methods
from . import gui_params


class PeakModel(QT.QAbstractItemModel):
    def __init__(self, parent =None, controller=None):
        QT.QAbstractItemModel.__init__(self,parent)
        self.controller = controller
        self.refresh_colors()
    
    def columnCount(self , parentIndex):
        return 4
        
    def rowCount(self, parentIndex):
        if not parentIndex.isValid() and self.controller.spike_label is not None:
            self.visible_ind, = np.nonzero(self.controller.spike_visible)
            n = self.visible_ind.size
            return n
        else :
            return 0
        
    def index(self, row, column, parentIndex):
        if not parentIndex.isValid():
            if column==0:
                childItem = row
            return self.createIndex(row, column, None)
        else:
            return QT.QModelIndex()
    
    def parent(self, index):
        return QT.QModelIndex()
    
    def data(self, index, role):
        if not index.isValid():
            return None
        
        if role not in (QT.Qt.DisplayRole, QT.Qt.DecorationRole):
            return
        
        col = index.column()
        row = index.row()
        #~ ind = self.visible_peak_labels.index[row]
        #~ label =  self.visible_peak_labels.iloc[row]
        #~ t_start = 0.
        
        abs_ind = self.visible_ind[row]
        
        seg_num = self.controller.spike_segment[abs_ind]
        peak_pos = self.controller.spike_index[abs_ind]
        peak_time = peak_pos/self.controller.dataio.sample_rate
        peak_label = self.controller.spike_label[abs_ind]
        
        if role ==QT.Qt.DisplayRole :
            if col == 0:
                return '{}'.format(abs_ind)
            elif col == 1:
                return '{}'.format(seg_num)
            elif col == 2:
                return '{:.4f}'.format(peak_time)
            elif col == 3:
                return '{}'.format(peak_label)
            else:
                return None
        elif role == QT.Qt.DecorationRole :
            if col != 0: return None
            if peak_label in self.icons:
                return self.icons[peak_label]
            else:
                return None
        else :
            return None
    
    def flags(self, index):
        if not index.isValid():
            return QT.Qt.NoItemFlags
        return QT.Qt.ItemIsEnabled | QT.Qt.ItemIsSelectable #| Qt.ItemIsDragEnabled

    def headerData(self, section, orientation, role):
        if orientation == QT.Qt.Horizontal and role == QT.Qt.DisplayRole:
            return  ['num', 'seg_num', 'time', 'cluster_label'][section]
        return
    
    def refresh_colors(self):
        self.icons = { }
        for k in self.controller.qcolors:
            color = self.controller.qcolors.get(k, QT.QColor( 'white'))
            pix = QT.QPixmap(10,10 )
            pix.fill(color)
            self.icons[k] = QT.QIcon(pix)
        
        #~ self.icons[-1] = QIcon(':/user-trash.png')
        
        self.layoutChanged.emit()
        
        
[docs]class PeakList(WidgetBase): """ **Peak List** show all detected peak for the catalogue construction. Here pintentionally peaks are not spikes already (even most of them are spikes) because supperposition of spikes are done here in catalogue in Peeler. Note: * If there are to many peaks, not all of them will have a extracted waveform. This why some peak are not labeled (-10) and nb_peak != nb_wveforms sometimes. * Peaks can belong to diffrents segment, a column indicate it. This is th full list of all peaks of all segment. * A right click open a ontext menu: * move one or several selected spike to trash * create a new cluster with one or several spikes * When you select one spike, this will auto zoom on **Trace View**, auto select the appriopriate segment and hilight the point on **ND Scatetr**. And vice versa. """ def __init__(self, controller=None, parent=None): WidgetBase.__init__(self, parent=parent, controller=controller) self.layout = QT.QVBoxLayout() self.setLayout(self.layout) self.label_title = QT.QLabel('') self.layout.addWidget(self.label_title) self.tree = QT.QTreeView(minimumWidth = 100, uniformRowHeights = True, selectionMode= QT.QAbstractItemView.ExtendedSelection, selectionBehavior = QT.QTreeView.SelectRows, contextMenuPolicy = QT.Qt.CustomContextMenu,) self.layout.addWidget(self.tree) self.tree.customContextMenuRequested.connect(self.open_context_menu) self.model = PeakModel(controller = controller) self.tree.setModel(self.model) self.tree.selectionModel().selectionChanged.connect(self.on_tree_selection) for i in range(self.model.columnCount(None)): self.tree.resizeColumnToContents(i) self.tree.setColumnWidth(0,80) self.refresh() def refresh(self): self.model.refresh_colors() nb_peak = self.controller.spikes.size if self.controller.some_waveforms is not None: nb_wf = self.controller.some_waveforms.shape[0] else: nb_wf = 0 self.label_title.setText('<b>All peaks {} - Nb waveforms {}</b>'.format(nb_peak, nb_wf)) def on_tree_selection(self): self.controller.spike_selection[:] = False for index in self.tree.selectedIndexes(): if index.column() == 0: ind = self.model.visible_ind[index.row()] self.controller.spike_selection[ind] = True self.spike_selection_changed.emit() def on_spike_selection_changed(self): self.tree.selectionModel().selectionChanged.disconnect(self.on_tree_selection) row_selected, = np.nonzero(self.controller.spike_selection[self.model.visible_ind]) if row_selected.size>100:#otherwise this is verry slow row_selected = row_selected[:10] # change selection self.tree.selectionModel().clearSelection() flags = QT.QItemSelectionModel.Select #| QItemSelectionModel.Rows itemsSelection = QT.QItemSelection() for r in row_selected: for c in range(2): index = self.tree.model().index(r,c,QT.QModelIndex()) ir = QT.QItemSelectionRange( index ) itemsSelection.append(ir) self.tree.selectionModel().select(itemsSelection , flags) # set selection visible if len(row_selected)>=1: index = self.tree.model().index(row_selected[0],0,QT.QModelIndex()) self.tree.scrollTo(index) self.tree.selectionModel().selectionChanged.connect(self.on_tree_selection) def open_context_menu(self): menu = QT.QMenu() act = menu.addAction('Move peak selection to trash') act.triggered.connect(self.move_peak_selection_to_trash) act = menu.addAction('Make cluster with selection') act.triggered.connect(self.make_new_cluster) ##menu.popup(self.cursor().pos()) menu.exec_(self.cursor().pos()) def move_peak_selection_to_trash(self): self.controller.change_spike_label(self.controller.spike_selection, -1) self.refresh() self.spike_label_changed.emit() def make_new_cluster(self): self.controller.change_spike_label(self.controller.spike_selection, max(self.controller.cluster_labels)+1) self.refresh() self.spike_label_changed.emit()
[docs]class ClusterPeakList(ClusterBaseList): """ **Cluster list** is the central widget for actions for clusters : make them visible, merge, trash, sort, split, change color, ... A context menu with right propose: * **Reset colors** * **Show all** * **Hide all** * **Re-label cluster by rms**: this re order cluster so that 0 have the bigger rms and N the lowest. * **Feature projection with all**: this re compute feature projection (equivalent to left toolbar) * **Feature projection with selection**: this re compute feature projection but take in account only selected usefull when you have doubt on small cluster and want a specifit PCA because variance is absord by big ones. * **Move selection to trash** * **Merge selection**: re label spikes in the same cluster. * **Select on peak list**: a spike as selected for theses clusters. * **Tag selection as same cell**: in case of burst some cell can have diffrent waveform shape leading to diferents cluster but with same ratio. If you have that do not merge clusters because the Peeler wll fail. Prefer tag 2 cluster as same cell. * **Split selection**: try to split only selected cluster. Double click on a row make invisible all others except this one. Cluster can be visualy ordered by some criteria (rms, amplitude, nb peak, ...) This is useless to explore cluster few peaks, or big amplitudes, ... Negative labels are reserved: * -1 is thrash * -2 is noise snippet * -10 unclassified (because no waveform associated) * -9 Alien """ _special_label = sorted(list(labelcodes.to_name.keys())) def make_menu(self): self.menu = QT.QMenu() act = self.menu.addAction('Reset colors') act.triggered.connect(self.reset_colors) act = self.menu.addAction('Show all') act.triggered.connect(self.show_all) act = self.menu.addAction('Hide all') act.triggered.connect(self.hide_all) act = self.menu.addAction('Re-label cluster by rms') act.triggered.connect(self.order_clusters) act = self.menu.addAction('Feature projection with all') act.triggered.connect(self.pc_project_all) act = self.menu.addAction('Feature projection with selection') act.triggered.connect(self.pc_project_selection) act = self.menu.addAction('Move cluster selection to trash') act.triggered.connect(self.move_cluster_selection_to_trash) act = self.menu.addAction('Merge selection') act.triggered.connect(self.merge_selection) act = self.menu.addAction('Select on peak list') act.triggered.connect(self.select_peaks_of_clusters) act = self.menu.addAction('Tag selected clusters as same cell') act.triggered.connect(self.selection_tag_same_cell) act = self.menu.addAction('Split selection') act.triggered.connect(self.split_selection) act = self.menu.addAction('Change color/annotation/tag') act.triggered.connect(self.change_color_annotation_tag) def _selected_spikes(self): selection = np.zeros(self.controller.spike_label.shape[0], dtype = bool) for k in self.selected_cluster(): selection |= self.controller.spike_label == k return selection def reset_colors(self): self.controller.refresh_colors(reset = True) self.refresh() self.colors_changed.emit() def order_clusters(self): self.controller.order_clusters() self.refresh() self.spike_label_changed.emit() def pc_project_all(self, selection=None): method, kargs = open_dialog_methods(gui_params.features_params_by_methods, self) if method is None: return self.controller.project(method=method, selection=selection, **kargs) self.refresh() self.spike_label_changed.emit() def pc_project_selection(self): self.pc_project_all(selection=self._selected_spikes()) def move_cluster_selection_to_trash(self): mask = np.zeros(self.controller.spike_label.size, dtype='bool') for k in self.selected_cluster(): #~ mask = self.controller.spike_label == k mask |= self.controller.spike_label == k self.controller.change_spike_label(mask, labelcodes.LABEL_TRASH) self.refresh() self.spike_label_changed.emit() def merge_selection(self): label_to_merge = self.selected_cluster() self.controller.merge_cluster(label_to_merge) self.refresh() self.spike_label_changed.emit() def split_selection(self): #TODO bug when not n_clusters n = len(self.selected_cluster()) if n!=1: return label_to_split = self.selected_cluster()[0] method, kargs = open_dialog_methods(gui_params.cluster_params_by_methods, self) if method is None: return self.controller.split_cluster(label_to_split, method=method, **kargs) #order_clusters=True, self.refresh() self.spike_label_changed.emit() def selection_tag_same_cell(self): labels_to_group = self.selected_cluster() self.controller.tag_same_cell(labels_to_group) self.refresh() self.cluster_tag_changed.emit() def select_peaks_of_clusters(self): self.controller.spike_selection[:] = self._selected_spikes() self.refresh() self.spike_selection_changed.emit() def change_color_annotation_tag(self): labels = self.selected_cluster() n = len(labels) if n!=1: return k = labels[0] clusters = self.controller.clusters ind = np.searchsorted(clusters['cluster_label'], k) color = QT.QColor(self.controller.qcolors[k]) annotations = str(clusters[ind]['annotations']) tag = str(clusters[ind]['tag']) params_ = [ {'name': 'color', 'type': 'color', 'value': color}, {'name': 'annotations', 'type': 'str', 'value': annotations}, {'name': 'tag', 'type': 'list', 'value': tag, 'values':gui_params.possible_tags}, ] dia = ParamDialog(params_, title = 'Cluster {}'.format(k), parent=self) if dia.exec_(): d = dia.get() self.controller.set_cluster_attributes(k, **d) self.colors_changed.emit() self.refresh()