Making Matplotlib in Docker draws in Windows through WSL
A not so simple Docker example
The initial title of this post is "A Simple Docker Example", which is going to show how to plot a simple Python program into a Docker image and run it. But it turns out that I’ve chosen a not so simple one as an example.
Few days ago, I have annotated several hundred of bib images with their corresponding bib numbers for my Bib-Racer-Recognition project. I want to double check whether I have tagged the bib numbers correctly for the images, so I just created a simple program to show the original images and the corresponding bib numbers. It proved that this step is necessary as the program did help me spot some wrongly tagged images.
My PC is running Windows 10 and the Docker image is built on top of the official Python linux base image, so it makes sense to run the Linux container in Windows 10’s WSL (Windows Subsystem for Linux). This can let the container and the host talk in same language and let the WSL to handle the actual display issue with Windows. The steps include setting up WSL in Windows 10, prepare the Docker image, and correctly start and run the container.
WSL in Windows 10
Download and install VCXsrv
export DISPLAY=`grep -oP "(?<=nameserver ).+" /etc/resolv.conf`:0.0
The above command is to extract the IP address of the nameserver and export to the
DISPLAYenvironment variable. So you can
cat /etc/resolv.confto get the IP address and then
export DISPLAY=<IP address>:0.0.
Press Windows key and type “Allow an app through Windows Firewall”, check VcXsrv can talk through the Firewall for both public and private network.
Launch VcXsrv with “Disable access control” ticked.
Please refer to the original post for more details and discussion.
Since the program is just to show the images and the corresponding bib numbers, the only things need to be prepare are the images, the Python script file and the Dockerfile, as listed below:
. ├── Dockerfile ├── images │ ├── 00001.jpg │ ├── 00002.jpg │ ├── 00003.jpg │ ├── 00004.jpg │ ├── │ ├── ... │ ├── │ ├── 00576.jpg │ ├── 00577.jpg │ ├── 00578.jpg │ └── bib_numbers.txt └── verify_test_bib_numbers.py
Therefore the Dockerfile is very simple.
FROM python:latest RUN pip install matplotlib WORKDIR /app COPY verify_test_bib_numbers.py ./ CMD ["python", "./verify_test_bib_numbers.py", "./images/"]
We can then build the image by running:
docker build -t bib_verify .
Steps to start the container
This is the part that I spent most of my time, in researching the way to properly set the environment for the container to run the programs. The point is
pyplot, thus matplotlib in the container needs to be able to access the X11 socket of the host, in turns the X11 in WSL needs to utilize the GUI in Windows. The second part has already been set in the above section. This section is aiming at the first part.
In order to enable remote user can access the X server in the host (WSL), we should grant access control by using
sudo apt install x11-xserver-utils
Certainly, we can fine tune which should be allowed by
xhostinstead of allowing all the connections. There are more discussion here: docker _tkinter.TclError: couldn’t connect to display
When starting to run the container, we need to specify the
Unix-domain socket for container to communicate with the host by mounting the path in container
/tmp/.X11-unix to the same directory in the host, and also specify the DISPLAY environment variable. And hence is the below command to start a container:
docker run -it \ --mount type=bind,source="$(pwd)"/images,target=/app/images \ -v "/tmp/.X11-unix:/tmp/.X11-unix:rw" \ -e DISPLAY=$DISPLAY bib_verify
Settings in matplotlib
There are many discussion on setting the ‘backend’ in matplotlib, e.g. here and here, for rendering in different environments, such as saving to a file or rendering to a GUI. Basically, there are two common methods to do this:
- Check which matplotlibrc is used:
matplotlib.matplotlib_fname(). Then set environment variable
MATPLOTLIBRCto the path to that file, or place it at the same directory of your script file.
backendin matplotlibrc, e.g.
- Check which matplotlibrc is used:
maplotlib.use('TkAgg')at the beginning of your script.
However, I found these steps may not be necessary. If we take a look at
matplotlibrc, it is written as:
## The default backend. If you omit this parameter, the first ## working backend from the following list is used: ## MacOSX Qt5Agg Qt4Agg Gtk3Agg TkAgg WxAgg Agg
At least in my case,
TkAgg is automatically selected even the above steps have not been taken. Certainly you can also set it manually if necesary.
This not so simple Docker example did let me spend more than a half day effort to sort out the matplotlib rendering in Docker problem. It’s challenging but interesting at the same time. Source code can be found here and comments are welcome. Ciao!