2016-10-28 3 views
0

J'ai écrit un script Python censé exécuter une boucle qui lit les coordonnées 3D de l'entrée standard et affiche les coordonnées les plus récentes sous forme de nuage de points sur un mpl_toolkits.mplot3d.Axes3D.Le tracé interactif ne s'initialise pas avec l'entrée d'un autre programme

Il fonctionne quand j'entrer les coordonnées manuellement via l'invite (il donne un avertissement de dévalorisation bien):

$ 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) 

Il fonctionne également lorsque je nourris les coordonnées d'un fichier dans le programme:

$ 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 

Mais cela ne fonctionne pas lorsque je redirige la sortie du script de génération directement dans le script de traçage et que le script de génération exécute un time.sleep() après chaque itération:

$ python generate3d.py | python plot3d.py 

Le comportement de travail est, la fenêtre de figure est ouverte, ce qui affiche un nuage de points montrant un point. Le comportement ne fonctionne pas, la fenêtre de la figure est ouverte, mais elle n'affiche que l'arrière-plan gris, sans axes ni points.

Voici le code pour le script traçante:

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) 

Voici le code pour le script de génération:

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) 

J'ai observé que l'impact de la sleeptime dans le script de génération peut-être proportionnel au temps nécessaire à l'initialisation des axes. Avec un temps de sommeil proche de zéro, presque aucun temps d'initialisation n'est nécessaire. Avec un temps de sommeil à 0,1, il faut déjà du temps pour que le graphique apparaisse. Je n'ai pas encore exploré la latence des données affichées quand elle est enfin là.

Quelqu'un peut-il reproduire ce comportement? Quelqu'un peut-il m'aider à comprendre et à résoudre ce problème? Est-ce que je fais quelque chose de mal?

informations potentiellement utiles:

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

>>> matplotlib.__version__ 
'1.3.1' 

Répondre

1

je peux reproduire avec la version 1.4.3 matplotlib et python 2.7. La raison semble être due à python stdin fonctions de lecture de blocage jusqu'à ce que le tuyau se ferme (décrit here),

L'origine de ce problème est de la manière mise en œuvre de ces mécanismes de lecture en Python (Voir la discussion on this issue de la question de Python traqueur). Dans Python 2.7.6, l'implémentation repose sur la bibliothèque stdio de C.

La solution qu'ils utilisent dans le lien est d'exécuter geneate3d.py comme un sous-processus et mettre le drapeau bloquante,

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() 

vous devez ajouter sys.stdout.flush() après l'impression dans generate3d.py.

Il semble que cela aurait été possible de mettre le drapeau en O_NONBLOCKsys.stdin avec quelque chose comme flags = fcntl(sys.stdin, F_GETFL) et fcntl(sys.stdin, F_SETFL, flags | O_NONBLOCK). Cependant, cela ne fonctionne pas pour moi.Je pense que ce n'est plus un problème dans python 3.0 selon le bug tracker.

+0

Merci pour vos commentaires! J'ai ajouté un 'sys.stdout.flush()' avant le sommeil dans 'generate3d.py' et le problème était parti. Pas besoin d'exécuter le processus en tant que sous-processus. – moooeeeep

0

Apparemment, le tampon des processus d'écriture sur la sortie standard n'a pas été vidé jusqu'à ce qu'il atteigne une certaine taille. Ajout d'un rinçage explicite résolu le problème:

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) 

Sinon, le script original peut être exécuté en mode unbuffered Python:

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