Skip to content

Conversation

@hmaarrfk
Copy link
Contributor

This is my attempt at addressing users' ability to import their DeepLabCut environment in both a Jupyter noteook and using the provided GUI (which is really useful!)

I still need to test it on a variety of configurations, but this makes wx a truely optional dependency, and doesn't affect the behavior of matplotlib until the user calls the GUI launching script.

Closes #1548

@hmaarrfk hmaarrfk force-pushed the delay_setting_the_backend branch 3 times, most recently from 2ea36bb to 30f28c6 Compare March 21, 2022 20:16
@MMathisLab MMathisLab requested review from AlexEMG and jeylau March 22, 2022 20:12
@jeylau
Copy link
Contributor

jeylau commented Mar 23, 2022

I like your solution @hmaarrfk!

@jeylau jeylau added the enhancement New feature or request label Mar 23, 2022
@jeylau jeylau self-assigned this Mar 23, 2022
@jeylau jeylau requested review from MMathisLab and aristotelis-economides and removed request for AlexEMG and jeylau March 23, 2022 09:51
@hmaarrfk hmaarrfk changed the title WIP: Delay setting the matplotlib backend until the user asks to launch the GUI Delay setting the matplotlib backend until the user asks to launch the GUI Mar 23, 2022
@hmaarrfk
Copy link
Contributor Author

Thank you. I'm not sure if you have other entry points you want to add a similar thing to. This seems to work for me for launching the main GUI.

Let me know if there is somewhere i should add a release note.

@aristotelis-economides
Copy link
Contributor

aristotelis-economides commented Mar 24, 2022

This is a very lean solution, nice @hmaarrfk !

While python -m deeplabcut works nicely, does this work for you?

import deeplabcut
deeplabcut.launch_dlc()

Because in my case I get Cannot load backend 'WxAgg' which requires the 'wx' interactive framework, as 'qt' is currently running . ('qt' is the default backend in my Linux OS)

@hmaarrfk
Copy link
Contributor Author

Do you have anything in your python rc or ipython rc file

@hmaarrfk
Copy link
Contributor Author

I think you also mean to say;

import deeplabcut
deeplabcut.launch_dlc()

since launch_gui does not exist

@hmaarrfk hmaarrfk marked this pull request as ready for review March 24, 2022 11:38
@hmaarrfk hmaarrfk force-pushed the delay_setting_the_backend branch from 30f28c6 to 93fc79b Compare March 24, 2022 11:39
@hmaarrfk
Copy link
Contributor Author

can you confirm that you are running things from the terminal and not something like the qtconsole, spyder, or jupyter?

@aristotelis-economides
Copy link
Contributor

I am running from ipython in a z-shell terminal in Arch OS. If I run with pure python it works, but many users use ipython in their workflow I think..

@hmaarrfk
Copy link
Contributor Author

Hmm, you are right. Thank you for specifying ipython.

ipython has many strangeness. My understanding was that

$ ipython
In [1]: import deeplabcut
In [2]: deeplabcut.launch_dlc()

was not the suggested way to start up deeplabcut.

In fact, in ipython, if I run

$ ipython
In [1]: import deeplabcut; deeplabcut.launch_dlc()

all on one line (as my first line), it seems to work.

There might be something else setting the matplotlib backend that I am unaware of. My guess is somewhere in the ipython stack. Detects if matplotlib has been "imported" and sets the backend to qt for figures.

@aristotelis-economides
Copy link
Contributor

aristotelis-economides commented Mar 24, 2022

$ ipython
In [1]: import deeplabcut; deeplabcut.launch_dlc()

this works indeed! I agree that ipython has many peculiarities. Thank you for looking into it! :)

@jeylau jeylau self-requested a review March 24, 2022 14:16
@hmaarrfk
Copy link
Contributor Author

@intergalactic-mammoth I feel like there is something else that is actually going on with matplotlib that is causing the selection of strange backend.

I've seen that with some functions, unrelated display, the backend would get selected.

For me, it happened to try to select the qtpy + PyQt5 backend, when I didn't have PyQt5 installed (I had PySide2 installed).

@aristotelis-economides
Copy link
Contributor

@intergalactic-mammoth I feel like there is something else that is actually going on with matplotlib that is causing the selection of strange backend.

I've seen that with some functions, unrelated display, the backend would get selected.

For me, it happened to try to select the qtpy + PyQt5 backend, when I didn't have PyQt5 installed (I had PySide2 installed).

Yes it is peculiar, I also tried to look into it, but didn't reach to a proper understanding of what is happening. Thank coming back to give extra info :) let;s see if we will decode ipython's mysteries...

@hmaarrfk hmaarrfk changed the title Delay setting the matplotlib backend until the user asks to launch the GUI WIP: Delay setting the matplotlib backend until the user asks to launch the GUI Mar 31, 2022
@hmaarrfk
Copy link
Contributor Author

Ok.... so I finally understand what is going. TLDR, anybody running the code:

from matplotlib import pyplot as plt

before deeplabcut will effectively stop setting WxAgg as the backend.

Minimum reproducible code:

# Run
from matplotlib import pyplot
# one at a  
import matplotlib
# time
matplotlib.use("WxAgg")
In [1]: from matplotlib import pyplot

In [2]: import matplotlib

In [3]: matplotlib.use("WxAgg")
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
Input In [3], in <cell line: 1>()
----> 1 matplotlib.use("WxAgg")

File ~/mambaforge/envs/dev/lib/python3.9/site-packages/matplotlib/__init__.py:1144, in use(backend, force)
   1139 if plt is not None:
   1140     try:
   1141         # we need this import check here to re-raise if the
   1142         # user does not have the libraries to support their
   1143         # chosen backend installed.
-> 1144         plt.switch_backend(name)
   1145     except ImportError:
   1146         if force:

File ~/mambaforge/envs/dev/lib/python3.9/site-packages/matplotlib/pyplot.py:275, in switch_backend(newbackend)
    272     current_framework = cbook._get_running_interactive_framework()
    273     if (current_framework and required_framework
    274             and current_framework != required_framework):
--> 275         raise ImportError(
    276             "Cannot load backend {!r} which requires the {!r} interactive "
    277             "framework, as {!r} is currently running".format(
    278                 newbackend, required_framework, current_framework))
    280 _log.debug("Loaded backend %s version %s.",
    281            newbackend, backend_mod.backend_version)
    283 rcParams['backend'] = rcParamsDefault['backend'] = newbackend

ImportError: Cannot load backend 'WXAgg' which requires the 'wx' interactive framework, as 'qt' is currently running

So there are 2 things:

  1. You could consider from matploltib import pyplot to be a bug in production code.
  2. But..... Why would they create something so good if it is bad.....

I mean ultimately, you could work hard to get rid of this code in DeepLabCut. But..... will you get rid of it everywhere where people use deeplabcut?

I'm ok with this patch as is. If a DeepLabCut developer wants to merge it, I can unmark it as draft (or maybe they can?) and merge.

Link to matplotlib reference where this is explained:
https://github.com/matplotlib/matplotlib/blob/da9533d507c1f06ceee5078c9cbbd99897809d31/lib/matplotlib/__init__.py#L1137

@hmaarrfk
Copy link
Contributor Author

And for reference, I think this will be fixed in the next release of matplotlib:
matplotlib/matplotlib#22005

@aristotelis-economides
Copy link
Contributor

@hmaarrfk great detective work really, thank you so much! 💪🙏🏻

@tacaswell
Copy link

Reading this I think the explanation of what is going on here is:

  • in mpl <3.6 we resolve the selected backend on import, but do not start any event loops until the first figure is made
  • if you are in IPython we let IPython know about the backend and it sets up the pt imput hooks (and in "plain" python or readline based Python this is done by via a different API, but same story and sometimes "natively" by the GUI binding libraries)
  • the default backend on Matplotlib is "the best we can find" which iterates though a list of GUI backends until it finds one that works and goes with that
  • when switching the backend Matplotlib checks if it can see a GUI framework which is running an event loop and won't select anything else (because while theoretically possible to have an input hook that handles alternating between more than one GUI framework and the prompt.....that seems like a bad idea).
  • in the qt pt input hook IPython will start a Qapplication if there is not one running

So if you run everything as one line, then you set the qt inputhook, set the wx inputhook, then let the wx inputhook run and everything works.

If you do this in multiple lines after the first one the qt inputhook fires, the QAppliaction gets created, and Matplotlib refuses to switch backends.


On the Matplotlib side we have made the automatic backend selection even lazier (not doing it until the user creates a figure).

On the IPython side, I think there is a strong case to have them fast-exit if no QApplication exists. In the past I advocated for the current situation, but on consideration I think I was wrong IPython should never make a QApplication. [could someone take the lead on pushing for that change 🙏🏻 I do not have the bandwidth at the moment].


🐑 Sorry about this, I am at least partially responsible for all of the parts that are interacting badly here.

@hmaarrfk
Copy link
Contributor Author

hmaarrfk commented Apr 1, 2022

@tacaswell thank you so much for spending the time troubleshooting, and all the previous time making it possible to lazily select the backend.

I'm not too sure I understand the issue to formulate it now to ipython.

For one, I don't have a minimum code that reproduces the issue (I got it down to 3 matplotlib lines!)

I will keep this in mind as I move forward.

I think building software systems is really difficult especially when you have to make optimizations based on your time and your own goals (or the projects goals).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Usage as a library

5 participants