Home > other >  How to use TkAgg backend for matplotlib in ubuntu image on Azure Devops?
How to use TkAgg backend for matplotlib in ubuntu image on Azure Devops?

Time:05-09

My matplotlib code is failing in ubuntu VMs in Azure DevOps; whereas the same code works in Azure Windows VMs and on my own ubuntu VM. This boiled-down Azure pipeline YAML reproduces the issue:

trigger:
- none

pool:
  vmImage: ubuntu-18.04

variables:
  python.version: '3.7'

steps:
- task: UsePythonVersion@0
  inputs:
    versionSpec: '$(python.version)'
    addToPath: true
  displayName: 'Use Python $(python.version)'

- bash: |
    pip install --upgrade pip &&
    pip install matplotlib &&
    pip list
  displayName: 'Install required packages'

- bash: |
    sudo apt-get install python3-tk
  displayName: 'Install Tcl/Tk GUI toolkit'

- task: PythonScript@0
  inputs:
    scriptSource: inline
    script: |
      import tkinter
      print('Tcl/Tk version: {}'.format(tkinter.Tcl().eval('info patchlevel')))
  displayName: 'Report Tcl/Tk version'

- task: PythonScript@0
  inputs:
    scriptSource: inline
    script: |
      import matplotlib
      import matplotlib.pyplot as plt
      
      print("Using: "   matplotlib.get_backend())
      
      plt.rcParams['toolbar'] = 'toolmanager'
      
      fig = plt.figure()
      ax = fig.add_subplot(111)
      
      tm = fig.canvas.manager.toolmanager
      tm.remove_tool('help')
  displayName: 'Remove help button from toolbar'

When the pipeline runs, it reports matplotlib 3.5.2 and Tcl/Tk version 8.6.8 are installed on the VM. But the final task fails with:

/home/vsts/work/_temp/5e3dce70-dff2-405e-b5ef-8dac88d22243.py:6: UserWarning: Treat the new Tool classes introduced in v1.5 as experimental for now; the API and rcParam may change in future versions.
  plt.rcParams['toolbar'] = 'toolmanager'
/home/vsts/work/_temp/5e3dce70-dff2-405e-b5ef-8dac88d22243.py:13: UserWarning: ToolManager does not control tool help
  tm.remove_tool('help')
Using: agg
Traceback (most recent call last):
  File "/home/vsts/work/_temp/5e3dce70-dff2-405e-b5ef-8dac88d22243.py", line 13, in <module>
    tm.remove_tool('help')
  File "/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/site-packages/matplotlib/backend_managers.py", line 210, in remove_tool
    tool.destroy()
AttributeError: 'NoneType' object has no attribute 'destroy'

Note the printed backend for matplotlib is agg rather than TkAgg. The same Python code works in my own ubuntu 18.04 VM after running sudo apt-get install python3-tk, and the code then reports TkAgg there.

So why doesn't this work in ubuntu in Azure DevOps? It looks like Tcl/Tk is not being installed or configured properly in the ubuntu VM.

Update If I insert matplotlib.use('TkAgg') after import matplotlib then the output does say Using: TkAgg. But I get this error instead at the fig = plt.figure() line:

ImportError: Cannot load backend 'TkAgg' which requires the 'tk' interactive framework, as 'headless' is currently running

I've tried setting the environment variables DISPLAY and MPLBACKEND as well, but to no avail.

CodePudding user response:

It turns out I needed to create a virtual display for Tcl/Tk to work correctly on the remote Azure DevOps VMs. I used Xvfb, as that's already installed on the ubuntu 18.04 agent (as per here). You also need to set the DISPLAY environment variable to point at the virtual display. Here's what worked for me in the end:

trigger:
- none

pool:
  vmImage: ubuntu-18.04

variables:
  python.version: '3.7'

steps:
- task: UsePythonVersion@0
  inputs:
    versionSpec: '$(python.version)'
    addToPath: true
  displayName: 'Use Python $(python.version)'

- bash: |
    pip install matplotlib &&
    pip list
  displayName: 'Install required packages'

- task: PythonScript@0
  inputs:
    scriptSource: inline
    script: |
      import tkinter
      print('Tcl/Tk version: {}'.format(tkinter.Tcl().eval('info patchlevel')))
  displayName: 'Report Tcl/Tk version'

- bash: |
    Xvfb :1 -screen 0 640x480x16 &
  displayName: 'Create virtual display'

- bash: |
    echo "##vso[task.setvariable variable=DISPLAY]:1.0"
  displayName: 'Set DISPLAY'

- task: PythonScript@0
  inputs:
    scriptSource: inline
    script: |
      import matplotlib
      import matplotlib.pyplot as plt
      
      print("Using: "   matplotlib.get_backend())
      
      plt.rcParams['toolbar'] = 'toolmanager'
      
      fig = plt.figure()
      ax = fig.add_subplot(111)
      
      tm = fig.canvas.manager.toolmanager
      tm.remove_tool('help')
  displayName: 'Remove help button from toolbar'

The output of the final task was then:

/home/vsts/work/_temp/b7d18caa-56d0-4438-9d8c-b6bbaa04935b.py:10: UserWarning: Treat the new Tool classes introduced in v1.5 as experimental for now; the API and rcParam may change in future versions.
  plt.rcParams['toolbar'] = 'toolmanager'
Using: TkAgg

Note matplotlib automatically detects the TkAgg backend; you don't have to tell it to use it. Two gotchas that caught me out: Xvfb starts with a capital X; and make sure you run it in the background!

  • Related