def selectionChanged(self, selected, deselected): super(ClusterTreeView, self).selectionChanged(selected, deselected) can_signal_selection = getattr(self, "can_signal_selection", True) can_signal_selection = can_signal_selection and getattr(self.model(), "can_signal_selection", True) if can_signal_selection: # emit the ClusterSelectionToChange signal clusters = self.selected_clusters() groups = self.selected_groups() allclusters = [] # groups first for group in groups: allclusters.extend([cl.clusteridx() for cl in self.model().get_clusters_in_group(group)]) # add clusters that are not in the selected groups, and # remove the others clusters_to_add = [] for cluster in clusters: if cluster not in allclusters: clusters_to_add.append(cluster) else: allclusters.remove(cluster) allclusters.extend(clusters_to_add) # remove duplicates while preserving the order clusters_unique = [] for clu in allclusters: if clu not in clusters_unique: clusters_unique.append(clu) clusters_unique = np.array(clusters_unique, dtype=np.int32) ssignals.emit(self, "ClusterSelectionToChange", clusters_unique)
def set_highlighted_spikes(self, spikes, do_emit=True): """Update spike colors to mark transiently selected spikes with a special color.""" if len(spikes) == 0: # do update only if there were previously selected spikes do_update = len(self.highlighted_spikes) > 0 self.highlight_mask[:] = 0 else: do_update = True self.highlight_mask[:] = 0 spikes_rel = self.spikes_rel[spikes] # from absolue indices to relative indices # spikes_rel = np.digitize(spikes, self.spike_ids) - 1 self.highlight_mask[spikes_rel] = 1 if do_update: # emit the HighlightSpikes signal if do_emit: ssignals.emit(self.parent, 'HighlightSpikes', spikes) # self.spike_ids[np.array(spikes, dtype=np.int32)]) self.paint_manager.set_data( highlight=self.highlight_mask, visual='features') # self.HighlightSpikes = QtCore.pyqtSignal(np.ndarray) # self.parent.emit(SpikySignals.HighlightSpikes, spikes) self.highlighted_spikes = spikes
def set_selected_spikes(self, spikes, do_emit=True): """Update spike colors to mark transiently selected spikes with a special color.""" if len(spikes) == 0: # do update only if there were previously selected spikes do_update = len(self.selected_spikes) > 0 self.selection_mask[:] = 0 else: do_update = True self.selection_mask[:] = 0 self.selection_mask[spikes] = 1 if do_update: # emit the SelectionSpikes signal if do_emit: if len(spikes) > 0: aspikes = self.data_manager.spike_ids[spikes] else: aspikes = spikes ssignals.emit(self.parent, 'SelectSpikes', aspikes) self.paint_manager.set_data( selection=self.selection_mask, visual='features') self.selected_spikes = spikes
def change_color(self): items = self.view.selected_items() if not items: return initial_color = items[0].color() if initial_color >= 0: initial_color = 255 * colors.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)) # test white: in this case, no color if rgb.sum() >= 2.999: nocolor = True else: nocolor = False # take the closest color in the palette i = np.argmin(np.abs(colors.COLORMAP - rgb).sum(axis=1)) # emit signal groups = np.array(self.view.selected_groups()) clusters = np.array(self.view.selected_clusters()) if len(groups) > 0: ssignals.emit(self, "ChangeGroupColorRequested", groups, i) if len(clusters) > 0: ssignals.emit(self, "ChangeClusterColorRequested", clusters, i)
def rename_group(self): groups = self.view.selected_groups() if groups: groupidx = groups[0] group = self.model.get_group(groupidx) name = group.name() text, ok = QtGui.QInputDialog.getText(self, "Group name", "Rename group:", QtGui.QLineEdit.Normal, name) if ok: ssignals.emit(self, "RenameGroupRequested", groupidx, text)
def load(self, filename, fileindex, probefile): # if hasattr(self, 'du'): # self.du.stop() self.provider = KlustersDataProvider() self.dh = self.provider.load(filename, fileindex=fileindex, probefile=probefile)#, progressbar=self.progressbar) # self.sdh = sdataio.SelectDataHolder(self.dh) # self.du = DataUpdater(self.sdh) # self.am = spiky.ActionManager(self.dh, self.sdh) ssignals.emit(self, 'FileLoaded')
def select_pair(self, parameter): self.cursor = 'ArrowCursor' # cx_rel, cy_rel = self.get_closest_cluster(parameter) cx, cy = self.get_closest_cluster(parameter) # cx = self.data_manager.clusters_unique[cx_rel] # cy = self.data_manager.clusters_unique[cy_rel] pair = np.unique(np.array([cx, cy])) ssignals.emit(self, 'ClusterSelectionToChange', pair)
def select_neighbor_feature(self, parameter): # print self.data_manager.projection coord, feature_dir = parameter # current channel and feature in the given coordinate proj = self.data_manager.projection[coord] if proj is None: proj = (0, coord) channel, feature = proj # next or previous feature feature = np.mod(feature + feature_dir, self.data_manager.fetdim) # select projection # self.select_projection((coord, channel, feature)) ssignals.emit(self.parent, 'ProjectionToChange', coord, channel, feature)
def select_channel(self, channel, coord=0): """Raise the ProjectionToChange signal when the channel is changed.""" # print type(channel) # return # if isinstance(channel, basestring): if channel.startswith('Extra'): channel = channel[6:] extra = True else: extra = False # try: channel = int(channel) if extra: channel += self.dh.nchannels #* self.dh.fetdim ssignals.emit(self, "ProjectionToChange", coord, channel, self.projection[coord][1])
def drag(self, target, sources): # get source ClusterItem nodes source_items = [] nodes = self.all_nodes() for node in nodes: if str(node) in sources and type(node) == ClusterItem and node not in source_items: source_items.append(node) # get the groupidx if the target is a group if type(target) == GroupItem: groupidx = target.groupidx() # else, if it is a cluster, take the corresponding group elif type(target) == ClusterItem: groupidx = self.get_groupidx(target.clusteridx()) else: # empty target return # clusters to move clusters = np.array([source.clusteridx() for source in source_items]) clusters = np.array([source.clusteridx() for source in source_items]) # emit signals ssignals.emit(self, "MoveClustersRequested", clusters, groupidx)
def slotProjectionToChange(self, sender, coord, channel, feature): ssignals.emit(sender, 'ProjectionChanged', coord, channel, feature)
def initialize_actions(self): """Initialize all global actions.""" # automatic projection action self.autoproj_action = QtGui.QAction("Automatic projection", self) self.autoproj_action.setIcon(spiky.get_icon("magic")) self.autoproj_action.setShortcut("P") self.autoproj_action.setStatusTip("Automatically choose the best " + "projection in the FeatureView.") self.autoproj_action.triggered.connect(lambda e: ssignals.emit(self, "AutomaticProjection"), QtCore.Qt.UniqueConnection) # open action self.open_action = QtGui.QAction("&Open", self) self.open_action.setShortcut("CTRL+O") self.open_action.setIcon(spiky.get_icon("open")) self.open_action.triggered.connect(self.open_file, QtCore.Qt.UniqueConnection) # open probe file action self.open_probe_action = QtGui.QAction("&Open probe file", self) self.open_probe_action.setShortcut("CTRL+SHIFT+O") self.open_probe_action.setIcon(spiky.get_icon("probe")) self.open_probe_action.triggered.connect(self.open_probefile, QtCore.Qt.UniqueConnection) # save action self.save_action = QtGui.QAction("&Save", self) self.save_action.setShortcut("CTRL+S") self.save_action.setIcon(spiky.get_icon("save")) self.save_action.triggered.connect(self.save_file, QtCore.Qt.UniqueConnection) # save action self.saveas_action = QtGui.QAction("Save &as", self) self.saveas_action.setShortcut("CTRL+SHIFT+S") self.saveas_action.setIcon(spiky.get_icon("saveas")) self.saveas_action.triggered.connect(self.saveas_file, QtCore.Qt.UniqueConnection) # exit action self.quit_action = QtGui.QAction("E&xit", self) self.quit_action.setShortcut("CTRL+Q") self.quit_action.triggered.connect(self.close, QtCore.Qt.UniqueConnection) # merge action self.merge_action = QtGui.QAction("Mer&ge", self) self.merge_action.setIcon(spiky.get_icon("merge")) self.merge_action.setShortcut("CTRL+G") self.merge_action.setEnabled(False) self.merge_action.triggered.connect(self.merge, QtCore.Qt.UniqueConnection) # split action self.split_action = QtGui.QAction("&Split", self) self.split_action.setIcon(spiky.get_icon("split")) self.split_action.setShortcut("CTRL+K") self.split_action.setEnabled(False) self.split_action.triggered.connect(self.split, QtCore.Qt.UniqueConnection) # DEL self.move_to_mua_action = QtGui.QAction("Move to &Multi-Unit", self) self.move_to_mua_action.setShortcut("Del") self.move_to_mua_action.setIcon(spiky.get_icon("multiunit")) self.move_to_mua_action.triggered.connect(self.move_to_mua, QtCore.Qt.UniqueConnection) self.move_to_mua_action.setEnabled(False) # SHIFT+DEL self.move_to_noise_action = QtGui.QAction("Move to &Noise", self) self.move_to_noise_action.setShortcut("Shift+Del") self.move_to_noise_action.setIcon(spiky.get_icon("noise")) self.move_to_noise_action.triggered.connect(self.move_to_noise, QtCore.Qt.UniqueConnection) self.move_to_noise_action.setEnabled(False) # undo action self.undo_action = QtGui.QAction("&Undo", self) self.undo_action.setShortcut("CTRL+Z") self.undo_action.setIcon(spiky.get_icon("undo")) self.undo_action.setEnabled(False) self.undo_action.triggered.connect(self.undo, QtCore.Qt.UniqueConnection) # redo action self.redo_action = QtGui.QAction("&Redo", self) self.redo_action.setShortcut("CTRL+Y") self.redo_action.setIcon(spiky.get_icon("redo")) self.redo_action.setEnabled(False) self.redo_action.triggered.connect(self.redo, QtCore.Qt.UniqueConnection) # override color action self.override_color_action = QtGui.QAction("Override &color", self) self.override_color_action.setShortcut("C") self.override_color_action.setIcon(spiky.get_icon("override_color")) self.override_color_action.triggered.connect(self.override_color, QtCore.Qt.UniqueConnection)
def select(self, clusters): self.dh.select_clusters(clusters) ssignals.emit(self.du, 'ClusterSelectionChanged', clusters)
def load(self, filename, fileindex=1, probefile=None):#, progressbar=None): # load XML self.holder = DataHolder() try: path = get_actual_filename(filename, 'xml', None) params = parse_xml(path, fileindex=fileindex) except Exception as e: raise Exception(("The XML file was not found and the data cannot " "be loaded.")) # klusters tests nchannels = params['nchannels'] nsamples = params['nsamples'] fetdim = params['fetdim'] freq = params['rate'] self.filename = filename self.fileindex = fileindex # if filename.endswith('_spiky'): # filename = filename.replace('_spiky', '') # spiky = True # else: # spiky = False # FEATURES # ------------------------------------------------- # features = load_text_fast(filename + ".fet.%d" % fileindex, np.int32, skiprows=1) path = get_actual_filename(filename, 'fet', fileindex) features, headers = load_text_pandas(path, np.int32, skiprows=1, returnheaders=True) features = np.array(features, dtype=np.float32) # Find out the number of extra features. nfet = int(headers[0]) # HACK: sometimes, problem with penultimate column due to double white space if features.shape[1] != nfet: features = np.hstack((features[:,:-2], features[:,-1].reshape((-1, 1)))) nextrafet = nfet - fetdim * nchannels # HACK: there are either 1 or 5 dimensions more than fetdim*nchannels # we can't be sure so we first try 1, if it does not work we try 5 # try: features = features.reshape((-1, nfet)) # except: # raise Exception(("The FET file was not found and the data cannot " # "be loaded.")) # log_debug("The number of columns is not fetdim (%d) x nchannels (%d) + 1." \ # % (fetdim, nchannels)) # try: # features = features.reshape((-1, fetdim * nchannels + 5)) # except: # log_debug("The number of columns is not fetdim (%d) x nchannels (%d) + 5, so I'm confused and I can't continue. Sorry :(" \ # % (fetdim, nchannels)) nspikes = features.shape[0] # CLUSTERS # ------------------------------------------------- try: # if spiky: # path = filename + "_spiky.clu.%d" % fileindex # else: # path = filename + ".clu.%d" % fileindex path = get_actual_filename(filename, 'clu', fileindex) # clusters = load_text(path, np.int32) clusters = load_text_pandas(path, np.int32) except Exception as e: log_warn("CLU file '%s' not found" % filename) clusters = np.zeros(nspikes + 1, dtype=np.int32) clusters[0] = 1 # nclusters = clusters[0] clusters = clusters[1:] # if progressbar: # progressbar.setValue(1) ssignals.emit(self, 'FileLoading', .2) # get the spiketimes spiketimes = features[:,-1].copy() # remove the last column in features, containing the spiketimes # features = features[:,:nchannels * fetdim] nextrafet = features.shape[1] - nchannels * fetdim # normalize normal features m = features[:,:-nextrafet].min() M = features[:,:-nextrafet].max() # force symmetry vx = max(np.abs(m), np.abs(M)) m, M = -vx, vx features[:,:-nextrafet] = -1+2*(features[:,:-nextrafet]-m)/(M-m) # normalize extra features m = features[:,-nextrafet:].min() M = features[:,-nextrafet:].max() # # force symmetry # vx = max(np.abs(m), np.abs(M)) # m, M = -vx, vx features[:,-nextrafet:] = -1+2*(features[:,-nextrafet:]-m)/(M-m) # if progressbar: # progressbar.setValue(2) ssignals.emit(self, 'FileLoading', .4) # MASKS # ------------------------------------------------- # first: try fmask try: # masks = load_text(filename + ".fmask.%d" % fileindex, np.float32, skiprows=1) path = get_actual_filename(filename, 'fmask', fileindex) masks = load_text_pandas(path, np.float32, skiprows=1) self.holder.masks_complete = masks masks = masks[:,:-1:fetdim] # masks = masks[::fetdim] except Exception as e: try: # otherwise, try mask # masks = load_text(filename + ".mask.%d" % fileindex, np.float32, skiprows=1) path = get_actual_filename(filename, 'mask', fileindex) masks = load_text_pandas(path, np.float32, skiprows=1) # masks = masks[:,:-1:fetdim] self.holder.masks_complete = masks masks = masks[:,:-1:fetdim] # masks = masks[::fetdim] except: # finally, warning and default masks (everything to 1) log_warn("MASK file '%s' not found" % filename) masks = np.ones((nspikes, nchannels)) self.holder.masks_complete = np.ones(features.shape) # if progressbar: # progressbar.setValue(3) ssignals.emit(self, 'FileLoading', .6) # WAVEFORMS # ------------------------------------------------- try: path = get_actual_filename(filename, 'spk', fileindex) waveforms = load_binary(path) # print waveforms.shape # print (nspikes, nsamples, nchannels) # DEBUG # nchannels = 32 # print waveforms.shape # print nspikes * nsamples * nchannels waveforms = waveforms.reshape((nspikes, nsamples, nchannels)) except IOError as e: log_warn("SPK file '%s' not found" % filename) waveforms = np.zeros((nspikes, nsamples, nchannels)) # if progressbar: # progressbar.setValue(4) ssignals.emit(self, 'FileLoading', .8) self.holder.freq = freq self.holder.nspikes = nspikes self.holder.nchannels = nchannels self.holder.nextrafet = nextrafet # construct spike times from random interspike interval self.holder.spiketimes = spiketimes self.holder.duration = spiketimes[-1] / float(self.holder.freq) # normalize waveforms at once waveforms = (waveforms - waveforms.mean()) waveforms = waveforms / np.abs(waveforms).max() self.holder.waveforms = waveforms self.holder.waveforms_info = dict(nsamples=nsamples) self.holder.fetdim = fetdim self.holder.features = features self.holder.masks = masks self.holder.clusters = clusters # create the groups info object # Default groups # GROUPS # -------------------------------------- try: path = get_actual_filename(filename, 'groups', fileindex) info = load_pickle(path) clusters_info = info['clusters_info'] groups_info = info['groups_info'] except: groups_info = { 0: dict(groupidx=0, name='Noise', color=0, spkcount=0), 1: dict(groupidx=1, name='Multi-unit', color=1, spkcount=0), 2: dict(groupidx=2, name='Good', color=2, spkcount=nspikes), } clusters_info = get_clusters_info(clusters, groupidx=2) nclusters = len(clusters_info) self.holder.nclusters = nclusters self.holder.clusters_info = dict( clusters_info=clusters_info, groups_info=groups_info, ) # c = Counter(clusters) # self.holder.clusters_counter = c probe = None try: if probefile: probe = np.loadtxt(probefile) except Exception as e: print(str(e)) self.holder.probe = dict(positions=probe) # cross correlograms nsamples_correlograms = 20 self.holder.correlograms_info = dict(nsamples=nsamples_correlograms) # self.holder.correlation_matrix = rdn.rand(nclusters, nclusters) # self.holder.correlation_matrix = np.array([[]]) # features = # self.holder.correlation_matrix = correlation_matrix(features, clusters) # self.holder.correlation_matrix_queue = CorrelationMatrixQueue(self.holder) # self.holder.correlation_matrix_queue.process() # TASKS.add('correlation_matrix_queue', CorrelationMatrixQueue, self.holder) tasks.TASKS.correlation_matrix_queue = inprocess(tasks.CorrelationMatrixQueue)(self.holder) tasks.TASKS.correlation_matrix_queue.process() return self.holder
def select_feature(self, coord, fet=0): """Select channel coord, feature fet.""" # raise the ProjectionToChange signal, and keep the previously # selected channel ssignals.emit(self, "ProjectionToChange", coord, self.projection[coord][0], fet)
def process_done(dh=None, doupdate=None, _result=None): ssignals.emit(None, 'CorrelationMatrixUpdated', _result)
def update_signal(self): """Raise the update signal, specifying that the correlogram view needs to be updated.""" ssignals.emit(self, 'CorrelogramsUpdated')
def add_group(self): ssignals.emit(self, "AddGroupRequested")
def remove_groups(self): ssignals.emit(self, "RemoveGroupsRequested", np.array(self.view.selected_groups()))