Пример #1
0
def test_widget_javascript_debounce(qtbot, event_name):
    phy.gui.qt.Debouncer.delay = 300

    widget = HTMLWidget(debounce_events=('select', ))
    widget.build()
    widget.show()
    qtbot.addWidget(widget)
    qtbot.waitForWindowShown(widget)
    _block(lambda: widget.html is not None)

    event_code = lambda i: r'''
    var event = new CustomEvent("phy_event", {detail: {name: '%s', data: {'i': %s}}});
    document.dispatchEvent(event);
    ''' % (event_name, i)

    _l = []

    def f(sender, *args):
        _l.append(args)

    connect(f, sender=widget, event=event_name)

    for i in range(5):
        widget.eval_js(event_code(i))
        qtbot.wait(10)
    qtbot.wait(500)

    assert len(_l) == (2 if event_name == 'select' else 5)

    # qtbot.stop()
    widget.close()

    phy.gui.qt.Debouncer.delay = 1
Пример #2
0
    def attach(self, gui):
        """Attach the view to the GUI.

        Perform the following:

        - Add the view to the GUI.
        - Update the view's attribute from the GUI state
        - Add the default view actions (auto_update, screenshot)
        - Bind the on_select() method to the select event raised by the supervisor.

        """

        # Add shortcuts only for the first view of any given type.
        shortcuts = self.shortcuts if not gui.list_views(self.__class__) else None

        gui.add_view(self, position=self._default_position)
        self.gui = gui

        # Set the view state.
        self.set_state(gui.state.get_view_state(self))

        self.actions = Actions(
            gui, name=self.name, view=self,
            default_shortcuts=shortcuts, default_snippets=self.default_snippets)

        # Freeze and unfreeze the view when selecting clusters.
        self.actions.add(
            self.toggle_auto_update, checkable=True, checked=self.auto_update, show_shortcut=False)
        self.actions.add(self.screenshot, show_shortcut=False)
        self.actions.add(self.close, show_shortcut=False)
        self.actions.separator()

        on_select = partial(self.on_select_threaded, gui=gui)
        connect(on_select, event='select')

        # Save the view state in the GUI state.
        @connect
        def on_close_view(view_, gui):
            if view_ != self:
                return
            logger.debug("Close view %s.", self.name)
            self._closed = True
            gui.remove_menu(self.name)
            unconnect(on_select)
            gui.state.update_view_state(self, self.state)
            self.canvas.close()
            gc.collect(0)

        @connect(sender=gui)
        def on_close(sender):
            gui.state.update_view_state(self, self.state)

        # HACK: Fix bug on macOS where docked OpenGL widgets were not displayed at startup.
        self._set_floating = AsyncCaller(delay=5)

        @self._set_floating.set
        def _set_floating():
            self.dock.setFloating(False)

        emit('view_attached', self, gui)
Пример #3
0
def _wait_until_table_ready(qtbot, table):
    b = Barrier()
    connect(b(1), event='ready', sender=table)

    table.show()
    qtbot.addWidget(table)
    qtbot.waitForWindowShown(table)
    b.wait()
Пример #4
0
    def _init_table(self, columns=None, value_names=None, data=None, sort=None):
        """Build the table."""

        columns = columns or ['id']
        value_names = value_names or columns
        data = data or []

        b = self.builder
        b.set_body_src('index.html')

        if is_high_dpi():  # pragma: no cover
            b.add_style('''

                /* This is for high-dpi displays. */
                body {
                    transform: scale(2);
                    transform-origin: 0 0;
                    overflow-y: scroll;
                    /*overflow-x: hidden;*/
                }

                input.filter {
                    width: 50% !important;
                }

            ''')

        b.add_style(_color_styles())

        self.data = data
        self.columns = columns
        self.value_names = value_names

        emit('pre_build', self)

        data_json = dumps(self.data)
        columns_json = dumps(self.columns)
        value_names_json = dumps(self.value_names)
        sort_json = dumps(sort)

        b.body += '''
        <script>
            var data = %s;

            var options = {
              valueNames: %s,
              columns: %s,
              sort: %s,
            };

            var table = new Table('table', options, data);

        </script>
        ''' % (data_json, value_names_json, columns_json, sort_json)
        self.build(lambda html: emit('ready', self))

        connect(event='select', sender=self, func=lambda *args: self.update(), last=True)
        connect(event='ready', sender=self, func=lambda *args: self._set_ready())
Пример #5
0
    def attach(self, gui):
        """Attach to the GUI."""

        # Make sure the selected field in cluster and similarity views are saved in the local
        # supervisor state, as this information is dataset-dependent.
        gui.state.add_local_keys(['ClusterView.selected'])

        # Create the cluster view and similarity view.
        self._create_views(gui=gui,
                           sort=gui.state.get('ClusterView',
                                              {}).get('current_sort', None))

        # Create the TaskLogger.
        self.task_logger = TaskLogger(
            cluster_view=self.cluster_view,
            similarity_view=self.similarity_view,
            supervisor=self,
        )

        connect(self._save_gui_state, event='close', sender=gui)
        gui.add_view(self.cluster_view, position='left', closable=False)
        gui.add_view(self.similarity_view, position='left', closable=False)

        # Create all supervisor actions (edit and view menu).
        self.action_creator.attach(gui)
        self.actions = self.action_creator.edit_actions  # clustering actions
        self.select_actions = self.action_creator.select_actions
        self.view_actions = gui.view_actions
        emit('attach_gui', self)

        # Call supervisor.save() when the save/ctrl+s action is triggered in the GUI.
        @connect(sender=gui)
        def on_request_save(sender):
            self.save()

        # Set the debouncer.
        self._busy = {}
        self._is_busy = False
        # Collect all busy events from the views, and sets the GUI as busy
        # if at least one view is busy.

        @connect
        def on_is_busy(sender, is_busy):
            self._busy[sender] = is_busy
            self._set_busy(any(self._busy.values()))

        @connect(sender=gui)
        def on_close(e):
            unconnect(on_is_busy, self)

        @connect(sender=self.cluster_view)
        def on_ready(sender):
            """Select the clusters from the cluster view state."""
            selected = gui.state.get('ClusterView', {}).get('selected', [])
            if selected:  # pragma: no cover
                self.cluster_view.select(selected)
Пример #6
0
 def _create_gui(cls):
     cls._gui = cls._controller.create_gui(do_prompt_save=False)
     s = cls._controller.supervisor
     b = Barrier()
     connect(b('cluster_view'), event='ready', sender=s.cluster_view)
     connect(b('similarity_view'), event='ready', sender=s.similarity_view)
     cls._gui.show()
     # cls._qtbot.addWidget(cls._gui)
     cls._qtbot.waitForWindowShown(cls._gui)
     b.wait()
Пример #7
0
    def attach(self, gui):
        """Attach the view to the GUI."""
        super(FeatureView, self).attach(gui)

        self.actions.add(self.toggle_automatic_channel_selection,
                         checked=not self.fixed_channels,
                         checkable=True)
        self.actions.add(self.clear_channels)
        self.actions.separator()

        connect(self.on_channel_click)
        connect(self.on_request_split)
Пример #8
0
def test_supervisor_split_2(gui, similarity):
    spike_clusters = np.array([0, 0, 1])

    supervisor = Supervisor(spike_clusters, similarity=similarity)
    supervisor.attach(gui)

    b = Barrier()
    connect(b('cluster_view'), event='ready', sender=supervisor.cluster_view)
    connect(b('similarity_view'), event='ready', sender=supervisor.similarity_view)
    b.wait()

    supervisor.actions.split([0])
    supervisor.block()
    _assert_selected(supervisor, [2, 3])
Пример #9
0
    def _create_views(self, gui=None, sort=None):
        """Create the cluster view and similarity view."""

        sort = sort or self._sort  # comes from either the GUI state or constructor

        # Create the cluster view.
        self.cluster_view = ClusterView(gui,
                                        data=self.cluster_info,
                                        columns=self.columns,
                                        sort=sort)
        # Update the action flow and similarity view when selection changes.
        connect(self._clusters_selected,
                event='select',
                sender=self.cluster_view)

        # Create the similarity view.
        self.similarity_view = SimilarityView(gui,
                                              columns=self.columns +
                                              ['similarity'],
                                              sort=('similarity', 'desc'))
        connect(self._get_similar_clusters,
                event='request_similar_clusters',
                sender=self.similarity_view)
        connect(self._similar_selected,
                event='select',
                sender=self.similarity_view)

        # Change the state after every clustering action, according to the action flow.
        connect(self._after_action, event='cluster', sender=self)
Пример #10
0
    def _set_supervisor(self):
        """Create the Supervisor instance."""
        # Load the new cluster id.
        new_cluster_id = self.context.load('new_cluster_id').get(
            'new_cluster_id', None)

        # Cluster groups.
        cluster_groups = self.model.cluster_groups

        # Create the Supervisor instance.
        supervisor = Supervisor(
            spike_clusters=self.model.spike_clusters,
            cluster_groups=cluster_groups,
            cluster_metrics=self.cluster_metrics,
            similarity=self.similarity_functions[self.similarity],
            new_cluster_id=new_cluster_id,
            context=self.context,
        )

        # Connect the `save_clustering` event raised by the supervisor when saving
        # to the model's saving functions.
        connect(self.on_save_clustering, sender=supervisor)

        @connect(sender=supervisor)
        def on_attach_gui(sender):
            @supervisor.actions.add(shortcut='shift+ctrl+k', set_busy=True)
            def recluster(cluster_ids=None):
                """Relaunch KlustaKwik on the selected clusters."""
                # Selected clusters.
                cluster_ids = supervisor.selected
                spike_ids = self.selector.select_spikes(cluster_ids)
                logger.info("Running KlustaKwik on %d spikes.", len(spike_ids))

                # Run KK2 in a temporary directory to avoid side effects.
                n = 10
                with TemporaryDirectory() as tempdir:
                    spike_clusters, metadata = cluster(
                        self.model,
                        spike_ids,
                        num_starting_clusters=n,
                        tempdir=tempdir,
                    )
                self.supervisor.split(spike_ids, spike_clusters)

            self.color_selector = supervisor.color_selector

        self.supervisor = supervisor
Пример #11
0
 def _eval(self, task):
     """Evaluate a task and call a callback function."""
     sender, name, args, kwargs = task
     logger.log(5, "Calling %s.%s(%s)", sender.__class__.__name__, name, args, kwargs)
     f = getattr(sender, name)
     callback = partial(self._callback, task)
     argspec = inspect.getfullargspec(f)
     argspec = argspec.args + argspec.kwonlyargs
     if 'callback' in argspec:
         f(*args, **kwargs, callback=callback)
     else:
         # HACK: use on_cluster event instead of callback.
         def _cluster_callback(tsender, up):
             self._callback(task, up)
         connect(_cluster_callback, event='cluster', sender=self.supervisor)
         f(*args, **kwargs)
         unconnect(_cluster_callback)
Пример #12
0
def supervisor(qtbot, gui, cluster_ids, cluster_groups, cluster_labels,
               similarity, tempdir):
    spike_clusters = np.repeat(cluster_ids, 2)

    s = Supervisor(
        spike_clusters,
        cluster_groups=cluster_groups,
        cluster_labels=cluster_labels,
        similarity=similarity,
        context=Context(tempdir),
        sort=('id', 'desc'),
    )
    s.attach(gui)
    b = Barrier()
    connect(b('cluster_view'), event='ready', sender=s.cluster_view)
    connect(b('similarity_view'), event='ready', sender=s.similarity_view)
    b.wait()
    return s
Пример #13
0
    def _init_table(self, columns=None, value_names=None, data=None, sort=None):
        """Build the table."""

        columns = columns or ['id']
        value_names = value_names or columns
        data = data or []

        b = self.builder
        b.set_body_src('index.html')

        b.add_style(_color_styles())

        self.data = data
        self.columns = columns
        self.value_names = value_names

        emit('pre_build', self)

        data_json = dumps(self.data)
        columns_json = dumps(self.columns)
        value_names_json = dumps(self.value_names)
        sort_json = dumps(sort)

        b.body += '''
        <script>
            var data = %s;

            var options = {
              valueNames: %s,
              columns: %s,
              sort: %s,
            };

            var table = new Table('table', options, data);

        </script>
        ''' % (data_json, value_names_json, columns_json, sort_json)
        self.build(lambda html: emit('ready', self))

        connect(event='select', sender=self, func=lambda *args: self.update(), last=True)
        connect(event='ready', sender=self, func=lambda *args: self._set_ready())
Пример #14
0
def test_supervisor_cluster_metrics(qtbot, gui, cluster_ids, cluster_groups,
                                    similarity, tempdir):
    spike_clusters = np.repeat(cluster_ids, 2)

    def my_metrics(cluster_id):
        return cluster_id**2

    cluster_metrics = {'my_metrics': my_metrics}

    mc = Supervisor(
        spike_clusters,
        cluster_groups=cluster_groups,
        cluster_metrics=cluster_metrics,
        similarity=similarity,
        context=Context(tempdir),
    )
    mc.attach(gui)
    b = Barrier()
    connect(b('cluster_view'), event='ready', sender=mc.cluster_view)
    connect(b('similarity_view'), event='ready', sender=mc.similarity_view)
    b.wait()

    assert 'my_metrics' in mc.columns
Пример #15
0
    def attach(self, gui):
        """Attach to the GUI."""

        # Make sure the selected field in cluster and similarity views are saved in the local
        # supervisor state, as this information is dataset-dependent.
        gui.state.add_local_keys(['ClusterView.selected'])

        # Create the cluster view and similarity view.
        self._create_views(
            gui=gui, sort=gui.state.get('ClusterView', {}).get('current_sort', None))

        # Create the TaskLogger.
        self.task_logger = TaskLogger(
            cluster_view=self.cluster_view,
            similarity_view=self.similarity_view,
            supervisor=self,
        )

        connect(self._save_gui_state, event='close', sender=gui)
        gui.add_view(self.cluster_view, position='left', closable=False)
        gui.add_view(self.similarity_view, position='left', closable=False)

        # Create the ClusterColorSelector instance.
        # Pass the state variables: color_field, colormap, categorical, logarithmic
        self.color_selector = ClusterColorSelector(
            cluster_meta=self.cluster_meta,
            cluster_metrics=self.cluster_metrics,
            cluster_ids=self.clustering.cluster_ids,
            **gui.state.get('color_selector', Bunch())
        )

        # Create all supervisor actions (edit and view menu).
        self.action_creator.attach(gui)
        self.actions = self.action_creator.edit_actions  # clustering actions
        self.select_actions = self.action_creator.select_actions
        self.view_actions = self.action_creator.view_actions
        emit('attach_gui', self)

        @connect(sender=self)
        def on_cluster(sender, up):
            # After a clustering action, get the cluster ids as shown
            # in the cluster view, and update the color selector accordingly.
            @self.cluster_view.get_ids
            def _update(cluster_ids):
                if cluster_ids is not None:
                    self.color_selector.set_cluster_ids(cluster_ids)

        # Call supervisor.save() when the save/ctrl+s action is triggered in the GUI.
        @connect(sender=gui)
        def on_request_save(sender):
            self.save()

        # Set the debouncer.
        self._busy = {}
        self._is_busy = False
        # Collect all busy events from the views, and sets the GUI as busy
        # if at least one view is busy.
        @connect
        def on_is_busy(sender, is_busy):
            self._busy[sender] = is_busy
            self._set_busy(any(self._busy.values()))

        @connect(sender=gui)
        def on_close(e):
            gui.state['color_selector'] = self.color_selector.state
            unconnect(on_is_busy)

        @connect(sender=self.cluster_view)
        def on_ready(sender):
            """Select the clusters from the cluster view state."""
            selected = gui.state.get('ClusterView', {}).get('selected', [])
            if selected:  # pragma: no cover
                self.cluster_view.select(selected)
Пример #16
0
    def __init__(
            self, spike_clusters=None, cluster_groups=None, cluster_metrics=None,
            cluster_labels=None, similarity=None, new_cluster_id=None, sort=None, context=None):
        super(Supervisor, self).__init__()
        self.context = context
        self.similarity = similarity  # function cluster => [(cl, sim), ...]
        self.actions = None  # will be set when attaching the GUI
        self._is_dirty = None
        self._sort = sort  # Initial sort requested in the constructor

        # Cluster metrics.
        # This is a dict {name: func cluster_id => value}.
        self.cluster_metrics = cluster_metrics or {}
        self.cluster_metrics['n_spikes'] = self.n_spikes

        # Cluster labels.
        # This is a dict {name: {cl: value}}
        self.cluster_labels = cluster_labels or {}

        self.columns = ['id']  # n_spikes comes from cluster_metrics
        self.columns += list(self.cluster_metrics.keys())
        self.columns += [
            label for label in self.cluster_labels.keys()
            if label not in self.columns + ['group']]

        # Create Clustering and ClusterMeta.
        # Load the cached spikes_per_cluster array.
        spc = context.load('spikes_per_cluster') if context else None
        self.clustering = Clustering(
            spike_clusters, spikes_per_cluster=spc, new_cluster_id=new_cluster_id)

        # Cache the spikes_per_cluster array.
        self._save_spikes_per_cluster()

        # Create the ClusterMeta instance.
        self.cluster_meta = create_cluster_meta(cluster_groups or {})
        # Add the labels.
        for label, values in self.cluster_labels.items():
            if label == 'group':
                continue
            self.cluster_meta.add_field(label)
            for cl, v in values.items():
                self.cluster_meta.set(label, [cl], v, add_to_stack=False)

        # Create the GlobalHistory instance.
        self._global_history = GlobalHistory(process_ups=_process_ups)

        # Create The Action Creator instance.
        self.action_creator = ActionCreator(self)
        connect(self._on_action, event='action', sender=self.action_creator)

        # Log the actions.
        connect(self._log_action, event='cluster', sender=self.clustering)
        connect(self._log_action_meta, event='cluster', sender=self.cluster_meta)

        # Raise supervisor.cluster
        @connect(sender=self.clustering)
        def on_cluster(sender, up):
            # NOTE: update the cluster meta of new clusters, depending on the values of the
            # ancestor clusters. In case of a conflict between the values of the old clusters,
            # the largest cluster wins and its value is set to its descendants.
            if up.added:
                self.cluster_meta.set_from_descendants(
                    up.descendants, largest_old_cluster=up.largest_old_cluster)
            emit('cluster', self, up)

        @connect(sender=self.cluster_meta)  # noqa
        def on_cluster(sender, up):  # noqa
            emit('cluster', self, up)

        connect(self._save_new_cluster_id, event='cluster', sender=self)

        self._is_busy = False
Пример #17
0
    def attach(self, gui):
        """Attach the GUI."""
        super(ClusterScatterView, self).attach(gui)

        def _make_action(dim, name):
            def callback():
                self.change_bindings(**{dim: name})

            return callback

        def _make_log_toggle(dim):
            def callback(checked):
                self.toggle_log_scale(dim, checked)

            return callback

        # Change the bindings.
        for dim in self._dims:
            view_submenu = 'Change %s' % dim

            # Change to every cluster info.
            for name in self.fields:
                self.actions.add(_make_action(dim, name),
                                 show_shortcut=False,
                                 name='Change %s to %s' % (dim, name),
                                 view_submenu=view_submenu)

            # Toggle logarithmic scale.
            self.actions.separator(view_submenu=view_submenu)
            self.actions.add(_make_log_toggle(dim),
                             checkable=True,
                             view_submenu=view_submenu,
                             name='Toggle log scale for %s' % dim,
                             show_shortcut=False,
                             checked=getattr(self, '%s_log_scale' % dim))

        self.actions.separator()
        self.actions.add(self.set_x_axis,
                         prompt=True,
                         prompt_default=lambda: self.x_axis)
        self.actions.add(self.set_y_axis,
                         prompt=True,
                         prompt_default=lambda: self.y_axis)
        self.actions.add(self.set_size,
                         prompt=True,
                         prompt_default=lambda: self.size)

        connect(self.on_select)
        connect(self.on_cluster)

        @connect(sender=self.canvas)
        def on_lasso_updated(sender, polygon):
            if len(polygon) < 3:
                return
            pos = range_transform([self.data_bounds], [NDC],
                                  self.marker_positions)
            ind = self.canvas.lasso.in_polygon(pos)
            cluster_ids = self.all_cluster_ids[ind]
            emit("request_select", self, list(cluster_ids))

        @connect(sender=self)
        def on_close_view(view_, gui):
            """Unconnect all events when closing the view."""
            unconnect(self.on_select)
            unconnect(self.on_cluster)
            unconnect(on_lasso_updated)

        if self.all_cluster_ids is not None:
            self.set_cluster_ids(self.all_cluster_ids)
        self._update_labels()
Пример #18
0
 def attach(self, gui):
     super(LassoMixin, self).attach(gui)
     connect(self.on_request_split)