Skip to content

Proposal for inter-figure event passing API #11678

Closed

Description

@wmvanvliet

Describe the new feature or enhancement

Proposed here: https://github.com/mne-tools/mne-python/wiki/GSoC-Ideas#2-event-system-for-brain-and-evoked-plots

We have an excellent 3D viewer for brain activations, and multiple ways of viewing evoked data (plot_topo, plot_topomap, plot_joint, etc.). We want to integrate these so that you can, for example, click on a time point in a Brain and have the evoked.plot_topomap update. This should be done using callbacks to allow for customizability.

Describe your proposed implementation

This is too long to fit in an issue description. See this gist: https://gist.github.com/wmvanvliet/fa0b0894d6acffc5fce1461e3a94ae4c

Describe possible alternatives

There are many possible alternatives to this API. For the current one, I aimed for minimal impact on existing code. To not enforce the usage of classes or wrapping matplotlib Figure objects in any way.

Additional context

No response

Activity

  1. larsoner commented on May 5, 2023

    @larsoner
    Member

    The proposal looks reasonable. A few minor comments:

    1. Looks pretty simple already!

    2. When subscribing, the API allows to pass
      on some additional parameters that to be passed along to the callback function.

      I don't think we need this, people should just use functools.partial.

    3. All we need to decide now is the public API and everything else should be private (like the global event dict or whatever).

    4. Implementation wise, we could have a single event channel object that is shared between figures. However, that makes it difficult to ever "unlink" channels. Unlinking is something that needs to happen for example when the user closes one of the figures.

      I'm not sure that's explicitly needed. The handler for the figure can use if self.closed: return style logic for the figure-closed case. And as long as we weakref the callback then it shouldn't prevent GC. But maybe I'm missing something.

    5. You use global event_channels in several places but I don't think you need it. If event_channels is mutable (like dict) then you can just assign vars etc. (it'll be in the __closure__ of the functions). The only time you really need global is for assignment like event_channels = ..., but as long as you do that at the module level rather than within a function you're good.

  2. added this to the 1.5 milestone on May 5, 2023
  3. wmvanvliet commented on May 6, 2023

    @wmvanvliet
    ContributorAuthor
    1. Thanks!
    2. Fair enough. That makes the API even simpler (edit done in the gist)
    3. Yes, but its good to have an actual implementation as well so we can make sure what we propose as API actually works and covers all use cases.
    4. You mean every event handler would need if self.closed: return boilerplate added to it? That doesn't sound like a good tradeoff.
    5. Correct. Changed in Gist.
  4. larsoner commented on May 8, 2023

    @larsoner
    Member

    I think my last comment is that we have to avoid the use of bare "event" here because it already means something in MNE. Maybe FigEvent or UIEvent or something similar.

    @drammock can you look at the gist linked at the top and see if you like the structure? I think I'm happy 👍

  5. drammock commented on May 8, 2023

    @drammock
    Member

    I didn't have a chance to try it out yet, will do tomorrow. The plan generally looks good to me though.

    questions

    1. Should sources for Event include our pyqtgraph-based raw browser?
    2. Nitpick: I think best practice is fig.canvas.draw_idle() (not .draw()) right?
    3. Can we do all this as methods on the figure objects rather than functions in the (already super crowded) MNE namespace?

    comment (after hardly any thought about it): I'd probably start off doing it as a mixin; there are some existing figure wrappers in mne/viz/_mpl_figure.py that could be leveraged too.

  6. drammock commented on May 8, 2023

    @drammock
    Member

    actually just tried it out. Super slick! Had to add a plt.ion() to get both figs to show. Got this traceback when closing the brain figure:

    Traceback (most recent call last):
      File "<ipython-input-1-125383a73ddd>", line 123, in delete_event_channel
        publish(fig, event='close')  # Notify subscribers of imminent close
      File "<ipython-input-1-125383a73ddd>", line 165, in publish
        if event.name not in channel:
    AttributeError: 'str' object has no attribute 'name'
    
  7. larsoner commented on May 9, 2023

    @larsoner
    Member

    Should sources for Event include our pyqtgraph-based raw browser?

    I think we should make as much stuff as helpful publish or subscribe, including matplotlib figures, the mne-qt-browser (possibly at a level of abstraction that it works for both MPL and PyQtGraph-based backends), Brain, etc. You could imagine it could be useful to have two mne-qt-browser instances browse two different Raw instances simultaneously, and we get it easily with this framework I think

    Can we do all this as methods on the figure objects rather than functions in the (already super crowded) MNE namespace?

    I'm guessing no because it would require subclassing everything, including stuff like the figure returned by plot_evoked. So it makes it less general/easy I think. But I do think the functions should live in mne.viz, that way mne. can be less crowded.

  8. modified the milestones: 1.5, 1.6 on Jul 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions