cortex-lab / phy

phy: interactive visualization and manual spike sorting of large-scale ephys data
BSD 3-Clause "New" or "Revised" License
318 stars 157 forks source link

AttributeError when using custom matplotlib view plugin #844

Closed mswallac closed 5 years ago

mswallac commented 5 years ago

Edit: Attempted to work around this with 312433ea4caf3cb6afa7ff9b3a5fa8369375dac4 but now receive the error below when trying to add the view associated with this plugin (even after undoing 312433ea4caf3cb6afa7ff9b3a5fa8369375dac4):


10:37:32.824 [W] actions:187          Error when executing action Add FeatureDensityView.
10:37:32.827 [D] actions:188          Traceback (most recent call last):
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\gui\actions.py", line 185, in wrapped
    return callback(*args)
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\gui\gui.py", line 356, in _create_and_add_view
    view.attach(self)
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\cluster\views\base.py", line 97, in attach
    gui.add_view(self, position=self._default_position)
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\gui\gui.py", line 397, in add_view
    widget = _try_get_matplotlib_canvas(view)
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\gui\gui.py", line 39, in _try_get_matplotlib_canvas
    view = FigureCanvasQTAgg(view.canvas.figure)
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\backends\backend_qt5agg.py", line 21, in __init__
    super().__init__(figure=figure)
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\backends\backend_qt5.py", line 232, in __init__
    self._update_figure_dpi()
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\backends\backend_qt5.py", line 257, in _update_figure_dpi
    self.figure._set_dpi(dpi, forward=False)
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\figure.py", line 477, in _set_dpi
    self.set_size_inches(w, h, forward=forward)
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\figure.py", line 913, in set_size_inches
    self.stale = True
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\artist.py", line 230, in stale
    self.stale_callback(self, val)
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\pyplot.py", line 583, in _auto_draw_if_interactive
    fig.canvas.draw_idle()
  File "C:\Users\labadmin\Miniconda3\envs\phy2a1\lib\site-packages\matplotlib\backends\backend_qt5.py", line 491, in draw_idle
    if not (self._draw_pending or self._is_drawing):
AttributeError: 'FigureCanvasQTAgg' object has no attribute '_draw_pending'

Original Issue: When attempting to use the custom matplotlib view plugin it seems like code in base.py will still attempt to treat the matplotlib canvas as an OpenGL canvas.

Here is the full error:


16:43:25.502 [E] __init__:58          An error has occurred (AttributeError): 'PlotCanvasMpl' object has no attribute 'set_lazy'
Traceback (most recent call last):
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\gui\widgets.py", line 249, in emitJS
    self._debouncer.submit(emit, *args)
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\gui\qt.py", line 501, in submit
    self.trigger()
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\gui\qt.py", line 516, in trigger
    f(*args, **kwargs)
  File "c:\users\labadmin\desktop\phy\phylib-master\phylib\utils\event.py", line 140, in emit
    res.append(f(sender, *args, **kwargs))
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\cluster\supervisor.py", line 817, in _clusters_selected
    emit('select', self, self.selected, **kwargs)
  File "c:\users\labadmin\desktop\phy\phylib-master\phylib\utils\event.py", line 140, in emit
    res.append(f(sender, *args, **kwargs))
  File "c:\users\labadmin\desktop\phy\phy\phy\phy\cluster\views\base.py", line 167, in on_select
    self.canvas.set_lazy(True)
AttributeError: 'PlotCanvasMpl' object has no attribute 'set_lazy'


Below is the code in .phy/plugins/MyPlugin.py (which should be unmodified from the example given in phy/docs/plugins.md):


from phy import IPlugin
from phy.cluster.views import ManualClusteringView  # Base class for phy views
from phy.plot.plot import PlotCanvasMpl  # matplotlib canvas

class FeatureDensityView(ManualClusteringView):
    plot_canvas_class = PlotCanvasMpl  # use matplotlib instead of OpenGL (the default)

    def __init__(self, features=None):
        """features is a function (cluster_id => Bunch(data, ...)) where data is a 3D array."""
        super(FeatureDensityView, self).__init__()
        self.features = features

    def on_select(self, cluster_ids=(), **kwargs):
        self.cluster_ids = cluster_ids
        # We don't display anything if no clusters are selected.
        if not cluster_ids:
            return

        # To simplify, we only consider the first PC component of the first 2 best channels.
        # Note that the features are in sparse format, where data's shape is
        # (n_spikes, n_best_channels, n_pcs). Only best channels for that clusters are
        # considered.
        # For this example, we just take the first 2 dimensions.
        x, y = self.features(cluster_ids[0]).data[:, :2, 0].T

        # We draw a 2D histogram with matplotlib.
        # The objects are:
        # - self.figure, a Figure instance
        # - self.canvas, a PlotCanvasMpl instance
        # - self.canvas.ax, an Axes object.
        self.canvas.ax.hist2d(x, y, 50)

        # Use this to update the matplotlib figure.
        self.canvas.update()

class MyPlugin(IPlugin):
    def attach_to_controller(self, controller):
        def create_feature_density_view():
            """A function that creates and returns a view."""
            return FeatureDensityView(features=controller.get_features)
        controller.view_creator['FeatureDensityView'] = create_feature_density_view

And for the sake of thoroughness here is .phy/phy_config.py:


# You can also put your plugins in ~/.phy/plugins/.

from phy import IPlugin

try:
    import phycontrib
except:
    pass

# Plugin example:
#
# class MyPlugin(IPlugin):
#     def attach_to_cli(self, cli):
#         # you can create phy subcommands here with click
#         pass

c = get_config()
c.Plugins.dirs = [r'C:\Users\labadmin\.phy\plugins/']
c.TemplateGUI.plugins = ['MyPlugin']
rossant commented 5 years ago

thanks for the bug report! this should be fixed now

mswallac commented 5 years ago

Sorry I didn't get the chance to try this myself until now. It seems I still get a similar error to the one mentioned in my edit. I created a new environment with the current state of the phy-dev branch and the phylib-master branch, and copied the updated version of the plugin from docs/plugins.md.

Edit: It's worth noting that in my case, this only happens when I click View>New view>Add FeatureDensityView. If I don't do this, the FeatureDensityView will not be displayed, and will give no errors.

Here is the current error:


15:15:06.090 [D] gui:390              Add view FeatureDensityView to GUI.
15:15:06.090 [W] actions:190          Error when executing action Add FeatureDensityView.
15:15:06.093 [D] actions:191          Traceback (most recent call last):
  File "c:\users\labadmin\desktop\phy\phy2_env\phy-dev\phy\gui\actions.py", line 188, in wrapped
    return callback(*args)
  File "c:\users\labadmin\desktop\phy\phy2_env\phy-dev\phy\gui\gui.py", line 356, in _create_and_add_view
    view.attach(self)
  File "c:\users\labadmin\desktop\phy\phy2_env\phy-dev\phy\cluster\views\base.py", line 154, in attach
    gui.add_view(self, position=self._default_position)
  File "c:\users\labadmin\desktop\phy\phy2_env\phy-dev\phy\gui\gui.py", line 397, in add_view
    widget = _try_get_matplotlib_canvas(view)
  File "c:\users\labadmin\desktop\phy\phy2_env\phy-dev\phy\gui\gui.py", line 39, in _try_get_matplotlib_canvas
    view = FigureCanvasQTAgg(view.canvas.figure)
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\backends\backend_qt5agg.py", line 21, in __init__
    super().__init__(figure=figure)
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\backends\backend_qt5.py", line 232, in __init__
    self._update_figure_dpi()
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\backends\backend_qt5.py", line 257, in _update_figure_dpi
    self.figure._set_dpi(dpi, forward=False)
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\figure.py", line 477, in _set_dpi
    self.set_size_inches(w, h, forward=forward)
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\figure.py", line 913, in set_size_inches
    self.stale = True
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\artist.py", line 230, in stale
    self.stale_callback(self, val)
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\pyplot.py", line 583, in _auto_draw_if_interactive
    fig.canvas.draw_idle()
  File "C:\Users\labadmin\Miniconda3\envs\phy2\lib\site-packages\matplotlib\backends\backend_qt5.py", line 491, in draw_idle
    if not (self._draw_pending or self._is_drawing):
AttributeError: 'FigureCanvasQTAgg' object has no attribute '_draw_pending'

rossant commented 5 years ago

That might be a matplotlib bug. What is your version of matplotlib? Could you update it to the latest one? Otherwise could you try upgrading to the development version of matplotlib?

mswallac commented 5 years ago

I am on version 3.1.0, which seems to be the latest. At the moment I'm having some issues installing the development version of matplotlib, so I'll have to resolve that first. Sorry for the delay.

Edit: For now I think I'll prototype my matplotlib plugins as scripts run from the IPython view.