You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Proposal for inter-figure event passing API #11678
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.
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.
The proposal looks reasonable. A few minor comments:
Looks pretty simple already!
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.
All we need to decide now is the public API and everything else should be private (like the global event dict or whatever).
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.
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.
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 👍
I didn't have a chance to try it out yet, will do tomorrow. The plan generally looks good to me though.
questions
Should sources for Event include our pyqtgraph-based raw browser?
Nitpick: I think best practice is fig.canvas.draw_idle() (not .draw()) right?
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.
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'
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.
Activity
larsoner commentedon May 5, 2023
The proposal looks reasonable. A few minor comments:
Looks pretty simple already!
I don't think we need this, people should just use
functools.partial.All we need to decide now is the public API and everything else should be private (like the global event dict or whatever).
I'm not sure that's explicitly needed. The handler for the figure can use
if self.closed: returnstyle logic for the figure-closed case. And as long as weweakrefthe callback then it shouldn't prevent GC. But maybe I'm missing something.You use
global event_channelsin several places but I don't think you need it. Ifevent_channelsis mutable (like dict) then you can just assign vars etc. (it'll be in the__closure__of the functions). The only time you really needglobalis for assignment likeevent_channels = ..., but as long as you do that at the module level rather than within a function you're good.wmvanvliet commentedon May 6, 2023
if self.closed: returnboilerplate added to it? That doesn't sound like a good tradeoff.larsoner commentedon May 8, 2023
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
FigEventorUIEventor 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 👍
drammock commentedon May 8, 2023
I didn't have a chance to try it out yet, will do tomorrow. The plan generally looks good to me though.
questions
Eventinclude our pyqtgraph-based raw browser?fig.canvas.draw_idle()(not.draw()) right?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.pythat could be leveraged too.drammock commentedon May 8, 2023
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:larsoner commentedon May 9, 2023
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
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 inmne.viz, that waymne.can be less crowded.