交互式绘图无法通过来自其他程序的输入进行初始化

时间:2016-10-28 07:12:09

标签: python matplotlib plot interactive

我编写了一个Python脚本,它应该运行一个循环,从标准输入读取3d坐标,并将最近读取的坐标显示为mpl_toolkits.mplot3d.Axes3D上的散点图。

当我通过提示手动输入坐标时它会起作用(虽然它提供了一些弃用警告):

$ python plot3d.py
.5 .5 .5
/usr/lib/pymodules/python2.7/matplotlib/backend_bases.py:2407: MatplotlibDeprecationWarning: Using default event loop until function specific to this GUI is implemented
  warnings.warn(str, mplDeprecation)

当我将文件中的坐标输入程序时,它也有效:

$ head generated
0.56 0.40 0.55
0.61 0.49 0.60
0.48 0.39 0.48
0.39 0.33 0.39
0.32 0.28 0.32
0.35 0.31 0.35
0.50 0.47 0.50
0.40 0.38 0.40
0.37 0.35 0.37
0.51 0.50 0.51
$ python plot3d.py < generated

但是当我将生成脚本的输出直接传递到绘图脚本中时,它不起作用,并且生成脚本在每次迭代后执行time.sleep()

$ python generate3d.py | python plot3d.py

工作行为是,打开图形窗口,显示一个散点图,显示一个点。不工作的行为是,图形窗口打开,但它只显示灰色背景,没有轴或点。

以下是绘图脚本的代码:

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

plt.ion()

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

plotted_items = None
while True:
    if plotted_items is not None:
        plotted_items.remove()
    try:
        x,y,z = map(float, raw_input().split())
    except:
        break
    else:
        plotted_items = ax.scatter([x],[y],[z])
        ax.set_xlim(-5, 5)
        ax.set_ylim(-5, 5)
        ax.set_zlim(-5, 5)
        plt.pause(0.1)

以下是生成脚本的代码:

from random import random
import time

last = None
while True:
    if last is None:
        last = [random(), random(), random()]
    offset = random()
    for i in range(len(last)):
        last[i] += 0.25 * (offset - last[i])
    print "%.2f %.2f %.2f" % tuple(last)
    # the value of this sleep duration influences how long it takes to initialize
    # the plot in the other script!?
    time.sleep(0.5) 

我观察到生成脚本中休眠时间的影响可能与轴初始化所需的时间成比例。休眠时间接近于零,几乎不需要初始化时间。睡眠时间为0.1时,图表的显示时间已经很长。 我还没有探讨最终显示数据的延迟。

有人可以重现这种行为吗?谁能帮助我理解和解决这个问题?我做错了吗?

潜在有用的信息:

$ lsb_release -d
Description:    Ubuntu 14.04.5 LTS
$ python --version
Python 2.7.6

>>> matplotlib.__version__
'1.3.1'

2 个答案:

答案 0 :(得分:1)

我可以使用matplotlib 1.4.3和python 2.7重现。原因似乎是由于python stdin读取函数阻塞直到管道关闭(描述为here),

  

这个问题的根源在于这些读取机制是用Python实现的(参见Python的问题跟踪器中的discussion on this issue)。在Python 2.7.6中,实现依赖于C的stdio库。

他们在链接中使用的解决方案是将geneate3d.py作为子进程运行并设置非阻塞标志,

from subprocess import Popen, PIPE
import time
from fcntl import fcntl, F_GETFL, F_SETFL
from os import O_NONBLOCK, read

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import sys

plt.ion()

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# run the shell as a subprocess:
p = Popen(['python', 'generate3d.py'],
        stdin = PIPE, stdout = PIPE, stderr = PIPE, shell = False)
# set the O_NONBLOCK flag of p.stdout file descriptor:
flags = fcntl(p.stdout, F_GETFL) # get current p.stdout flags
fcntl(p.stdout, F_SETFL, flags | O_NONBLOCK)
# issue command:
p.stdin.write('command\n')
# let the shell output the result:
time.sleep(0.1)

plotted_items = None
while True:
    if plotted_items is not None:
        try:
            plotted_items.remove()
        except ValueError:
            print(plotted_items)
            pass
    try:
        x,y,z = map(float, read(p.stdout.fileno(), 1024).split(' '))
    except OSError:
        time.sleep(0.5) 
    except:
        raise
    else:
        plotted_items = ax.scatter([x],[y],[z])
        ax.set_xlim(-5, 5)
        ax.set_ylim(-5, 5)
        ax.set_zlim(-5, 5)
        plt.pause(0.1)
        plt.draw()

您需要在sys.stdout.flush()打印后添加generate3d.py

似乎应该可以使用O_NONBLOCKsys.stdin之类的flags = fcntl(sys.stdin, F_GETFL)设置fcntl(sys.stdin, F_SETFL, flags | O_NONBLOCK)标记。但是,这对我不起作用。根据{{​​3}},我认为这不再是python 3.0中的问题。

答案 1 :(得分:0)

显然,标准输出上的写入进程缓冲区在达到一定大小之前不会被刷新。添加显式刷新解决了问题:

from random import random
import time
import sys

last = None
while True:
    if last is None:
        last = [random(), random(), random()]
    offset = random()
    for i in range(len(last)):
        last[i] += 0.25 * (offset - last[i])
    print "%.2f %.2f %.2f" % tuple(last)
    # flush before sleep to make sure the data is actually written on time
    sys.stdout.flush()
    time.sleep(1.0)

或者,原始脚本可以在Python的无缓冲模式下运行:

$ python -u generate3d.py | python plot3d.py