General plotting issues

I will start with a personal opinion that I have come to after a few years of using mne. My general perception of MNE-python is that it is engineered with the intent to be used within python notebooks, at least when it comes to visualizations. I was originally going to include some of these comments as part of a recent issue on Github, but given that these are more architectural discussion, I think they probably belong here.

This issue (at least it is for our group) has become problematic for us after trying to create some 3d visualizations of fnirs activity. We don’t tend to do much of our work in notebooks as we find maintainability and reproducibility to be difficult in such cases. Regardless, we prefer to do our work directly in .py files.

We first attempted to use plot_nirs_source_detector, a function created as part of the mne-nirs package, to visualize the results of multiple anova’s run for each sensor-detector pair. This function doesn’t perfectly fit the type of visualization we would like, but would be sufficient for our needs. However, to my knowledge, there is no proper way to use this function in a raw python file, as it is made with interactive python in mind (ie notebooks), and does not expose any necessary parameters (ie blocking), to prevent the code from running through and immediately closing the window. This function works as intended as part of a notebook, but not in a python file. I attempted to include blocking functionality in the code, but found it difficult, so decided to move on.

We then tried to approach our visualization using the mne.viz.Brain class, which has an explicit block parameter as part of its constructor :slight_smile: . This blocking works when performing a single call to this function (ie the constructor), but due to a bug causes subsequent calls to methods of the class to fail, as the renderer is cleaned up immediately after a call to the constructor when block is true. In regard to this class, I wonder how the block param is handled in general. Do calls to plotting methods of Brain objects constructed with block set to true also block, or should one expect to include this information with each method call? Additionally, looking at the internals of the show_view method, why doesn’t this method ever make a call to the class’s show method? I mean, this function is called show_view, but practically, this function only updates certain fields of the object. Those changes are then only reflected in the renderer if the update param is set. However, if block is set, the renderer is already cleaned up by that point, causing this call to fail. This methods fails to handle the case of either the show or block params being set in the object constructor. If this is the intended behavior then a more accurate signature would be update_view.

Overall, it seems as though mne works wonderfully when used in notebooks for visualizations, but is inconsistent with its ability to function properly outside of them. In some functions, block is present. In others, it is absent. Is implementing block an issue at the global/abstract level, or is this more of an architectural decision? Should I be doing visualization in notebooks and not python files?

I would appreciate any input on the subject and would love to know if there is some way that I can help improve the consistency of the codebase in regard to this issue.

Thanks, Alex

2 Likes

Without responding to your specific issue (I have never used 3D visualizations in a notebook), I agree that the way MNE handles plotting is not ideal. IMO, the main issue is that MNE tries to abstract away how Matplotlib works by trying to show figures immediately. This is well intentioned, but leads to several issues down the road. There have been several discussion and reports on this behavior, see e.g. RFC: plotting in notebooks with show=False · Issue #11528 · mne-tools/mne-python · GitHub (and the linked issues therein). Feel free to chime in if you think your issue is relevant/related (which I think it is).

1 Like