2016-10-28 2 views
0

Я написал сценарий 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) 

Я заметил, что влияние времени сна в генераторном сценарии может быть пропорциональное времени, требуемому для инициализации осей. При близком к нулю сдержанности почти не требуется время инициализации. С sleeptime в 0.1 это уже занимает много времени для появления диаграммы. Я еще не изучил задержку отображаемых данных, когда она наконец-то появилась.

Может ли кто-нибудь воспроизвести это поведение? Может ли кто-нибудь помочь мне понять и исправить это? Я делаю что-то неправильно?

Потенциально полезная информация:

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

>>> matplotlib.__version__ 
'1.3.1' 

ответ

1

я могу воспроизвести с Matplotlib версии 1.4.3 и Python 2.7. Причина, видимо, связано с питона stdin чтения функций не блокирующих до закрытий трубы (описано here),

Происхождение этой проблемы в том, как эти чтения механизмы реализуются в Python (см discussion on this issue от выпуска Пайтона трекер). В Python 2.7.6 реализация зависит от библиотеки stdio от C.

Раствор они используют в ссылке, чтобы запустить 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_NONBLOCK в sys.stdin с чем-то вроде flags = fcntl(sys.stdin, F_GETFL) и fcntl(sys.stdin, F_SETFL, flags | O_NONBLOCK). Однако это не работает для меня.Я думаю, что это уже не проблема в python 3.0 в соответствии с bug tracker.

+0

Спасибо за ваш вклад! Я добавил 'sys.stdout.flush()' перед сном в 'generate3d.py', и проблема исчезла. Нет необходимости запускать процесс в качестве подпроцесса. – moooeeeep

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 -u generate3d.py | python plot3d.py