2010-01-29 9 views
16

J'ai un script python qui après quelques calculs va générer deux fichiers de données formatés en entrée gnuplot. Comment appeler 'gnuplot' à partir de python?Appel de gnuplot à partir de python

Je veux envoyer la chaîne python suivante en entrée à gnuplot:

"plot '%s' with lines, '%s' with points;" % (eout,nout) 

où 'eout' et 'Nout' sont les deux noms de fichiers.

PS: Je préfère ne pas utiliser les modules Python supplémentaires (par exemple gnuplot-py.), Seule l'API standard.

Merci Vous

+0

Vous voulez appeler gnuplot en utilisant l'API (qui est en C, donc vous devez écrire un code de colle comme celui de gnuplot-py) ou simplement exécuter "gnuplot" dans shell? –

+0

Il suffit d'exécuter gnuplot dans le shell. –

Répondre

6

Une approche simple pourrait consister à écrire juste un troisième fichier contenant vos commandes gnuplot puis dire Python pour exécuter gnuplot avec cette fichier. Supposons que vous écrivez

"plot '%s' with lines, '%s' with points;" % (eout,nout) 

dans un fichier appelé tmp.gp. Ensuite, vous pouvez utiliser

from os import system, remove 
system('gnuplot tmp.gp') 
remove('tmp.gp') 
+3

Merci, ce hack a finalement fonctionné. Une mention: system ('gnuplot -persist tmp.gp') afin de conserver les fenêtres après la fin du script. –

22

Le module subprocess vous permet d'appeler d'autres programmes:

import subprocess 
plot = subprocess.Popen(['gnuplot'], stdin=subprocess.PIPE) 
plot.communicate("plot '%s' with lines, '%s' with points;" % (eout,nout)) 
+0

Après avoir lu votre exemple, j'ai écrit une fonction similaire, malheureusement, aucun résultat. (POpen = Popen, une faute de frappe je crois, mais ce n'était pas le problème) –

+1

Oui, POpen était une faute de frappe. En dehors de cela, peut-être que vous devez spécifier le chemin complet de gnuplot ou ajouter le commutateur '-persist' que vous mentionnez dans un autre commentaire. Vous pouvez également vérifier 'plot.returncode' pour les erreurs. – sth

14

sous-processus est expliqué très clairement sur Doug Hellemann Python Module of the Week

Cela fonctionne bien:

import subprocess 
proc = subprocess.Popen(['gnuplot','-p'], 
         shell=True, 
         stdin=subprocess.PIPE, 
         ) 
proc.stdin.write('set xrange [0:10]; set yrange [-2:2]\n') 
proc.stdin.write('plot sin(x)\n') 
proc.stdin.write('quit\n') #close the gnuplot window 

On pourrait aussi utiliser « communiquer », mais la fenêtre de tracé se ferme immédiatement, à moins une commande de pause gnuplot est utilisée

proc.communicate(""" 
set xrange [0:10]; set yrange [-2:2] 
plot sin(x) 
pause 4 
""") 
+2

Utilisez 'shell = False' et gnuplot' --persist' si vous voulez simplement montrer la fenêtre, et que Python ne la ferme pas ... – sdaau

4

J'essayais de faire quelque chose de similaire, mais en plus je voulais nourrir les données depuis python et sortir le fichier comme en tant que variable (donc ni les données ni le graphique ne sont des fichiers réels). C'est ce que je suis venu avec:

#! /usr/bin/env python 

import subprocess 
from sys import stdout, stderr 
from os import linesep as nl 

def gnuplot_ExecuteCommands(commands, data): 
    args = ["gnuplot", "-e", (";".join([str(c) for c in commands]))] 
    program = subprocess.Popen(\ 
     args, \ 
     stdin=subprocess.PIPE, \ 
     stdout=subprocess.PIPE, \ 
     stderr=subprocess.PIPE, \ 
     ) 
    for line in data: 
     program.stdin.write(str(line)+nl) 
    return program 

def gnuplot_GifTest(): 
    commands = [\ 
     "set datafile separator ','",\ 
     "set terminal gif",\ 
     "set output",\ 
     "plot '-' using 1:2 with linespoints, '' using 1:2 with linespoints",\ 
     ] 
    data = [\ 
     "1,1",\ 
     "2,2",\ 
     "3,5",\ 
     "4,2",\ 
     "5,1",\ 
     "e",\ 
     "1,5",\ 
     "2,4",\ 
     "3,1",\ 
     "4,4",\ 
     "5,5",\ 
     "e",\ 
     ] 
    return (commands, data) 

if __name__=="__main__": 
    (commands, data) = gnuplot_GifTest() 
    plotProg = gnuplot_ExecuteCommands(commands, data) 
    (out, err) = (plotProg.stdout, plotProg.stderr) 
    stdout.write(out.read()) 

Ce scénario Libère le graphique pour stdout comme la dernière étape principale. La ligne de commande équivalente (où le graphique est envoyé à « out.gif ») serait:

gnuplot -e "set datafile separator ','; set terminal gif; set output; plot '-' using 1:2 with linespoints, '' using 1:2 with linespoints" > out.gif 
1,1 
2,2 
3,5 
4,2 
5,1 
e 
1,5 
2,4 
3,1 
4,4 
5,5 
e 
2

est ici une classe qui fournit une interface pour wgnuplot.exe:

from ctypes import * 
import time 
import sys 
import os 

# 
# some win32 constants 
# 
WM_CHAR  = 0X0102 
WM_CLOSE = 16 
SW_HIDE  = 0 
STARTF_USESHOWWINDOW = 1 

WORD = c_ushort 
DWORD = c_ulong 
LPBYTE = POINTER(c_ubyte) 
LPTSTR = POINTER(c_char) 
HANDLE = c_void_p 

class STARTUPINFO(Structure): 
    _fields_ = [("cb",DWORD), 
     ("lpReserved",LPTSTR), 
     ("lpDesktop", LPTSTR), 
     ("lpTitle", LPTSTR), 
     ("dwX", DWORD), 
     ("dwY", DWORD), 
     ("dwXSize", DWORD), 
     ("dwYSize", DWORD), 
     ("dwXCountChars", DWORD), 
     ("dwYCountChars", DWORD), 
     ("dwFillAttribute", DWORD), 
     ("dwFlags", DWORD), 
     ("wShowWindow", WORD), 
     ("cbReserved2", WORD), 
     ("lpReserved2", LPBYTE), 
     ("hStdInput", HANDLE), 
     ("hStdOutput", HANDLE), 
     ("hStdError", HANDLE),] 

class PROCESS_INFORMATION(Structure): 
    _fields_ = [("hProcess", HANDLE), 
     ("hThread", HANDLE), 
     ("dwProcessId", DWORD), 
     ("dwThreadId", DWORD),] 

# 
# Gnuplot 
# 
class Gnuplot: 
    # 
    # __init__ 
    # 
    def __init__(self, path_to_exe): 
     # open gnuplot 
     self.launch(path_to_exe) 
     # wait till it's ready 
     if(windll.user32.WaitForInputIdle(self.hProcess, 1000)): 
      print "Error: Gnuplot timeout!" 
      sys.exit(1) 
     # get window handles 
     self.hwndParent = windll.user32.FindWindowA(None, 'gnuplot') 
     self.hwndText = windll.user32.FindWindowExA(self.hwndParent, None, 'wgnuplot_text', None) 



    # 
    # __del__ 
    # 
    def __del__(self): 
     windll.kernel32.CloseHandle(self.hProcess); 
     windll.kernel32.CloseHandle(self.hThread); 
     windll.user32.PostMessageA(self.hwndParent, WM_CLOSE, 0, 0) 


    # 
    # launch 
    # 
    def launch(self, path_to_exe): 
     startupinfo = STARTUPINFO() 
     process_information = PROCESS_INFORMATION() 

     startupinfo.dwFlags = STARTF_USESHOWWINDOW 
     startupinfo.wShowWindow = SW_HIDE 

     if windll.kernel32.CreateProcessA(path_to_exe, None, None, None, False, 0, None, None, byref(startupinfo), byref(process_information)): 
      self.hProcess = process_information.hProcess 
      self.hThread = process_information.hThread 
     else: 
      print "Error: Create Process - Error code: ", windll.kernel32.GetLastError() 
      sys.exit(1) 



    # 
    # execute 
    # 
    def execute(self, script, file_path): 
     # make sure file doesn't exist 
     try: os.unlink(file_path) 
     except: pass 

     # send script to gnuplot window 
     for c in script: windll.user32.PostMessageA(self.hwndText, WM_CHAR, ord(c), 1L) 

     # wait till gnuplot generates the chart 
     while(not (os.path.exists(file_path) and (os.path.getsize(file_path) > 0))): time.sleep(0.01) 
2

Je suis un peu en retard, mais comme il m'a fallu du temps pour le faire fonctionner, peut-être que ça vaut la peine de mettre une note. Les programmes fonctionnent avec Python 3.3.2 sous Windows.

Notez que les octets sont utilisés partout, pas les chaînes (par ex.b « parcelle x », pas simplement « complot x »), mais dans le cas où il est un problème, il suffit de faire quelque chose comme:

"plot x".encode("ascii") 

Première solution: utiliser communiquer envoyer tout, et à proximité quand il est fait. Il ne faut pas oublier pause, ou la fenêtre est fermée à la fois. Cependant, ce n'est pas un problème si gnuplot est utilisé pour stocker des images dans des fichiers. Deuxième solution: envoyez les commandes l'une après l'autre en utilisant stdin.write (...). Mais, ne pas oublier flush! (c'est ce que je n'ai pas compris au début) Et utilisez terminez pour fermer la connexion et gnuplot lorsque le travail est terminé.

from subprocess import * 
path = "C:\\app\\gnuplot\\bin\\gnuplot" 
p = Popen([path], stdin=PIPE, stdout=PIPE) 

p.stdin.write(b"splot x*y\n") 
p.stdin.flush() 
... 
p.stdin.write(b"plot x,x*x\n") 
p.stdin.flush() 
... 
p.terminate() 
2

Je suis allé avec la suggestion de Ben comme je l'informatique des graphiques à partir d'un travail de céleri et a constaté qu'il verrouille en position relevée lors de la lecture de la sortie standard. Je l'ai redessiné comme si en utilisant StringIO pour créer le fichier destiné à stdin et subprocess.communicate pour obtenir le résultat immédiatement via stdout, aucune lecture requise.


from subprocess import Popen, PIPE 
from StringIO import StringIO            
from os import linesep as nl 

def gnuplot(commands, data):              
    """ drive gnuplot, expects lists, returns stdout as string """    

    dfile = StringIO()               
    for line in data:               
     dfile.write(str(line) + nl)            

    args = ["gnuplot", "-e", (";".join([str(c) for c in commands]))]    
    p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)      

    dfile.seek(0)                
    return p.communicate(dfile.read())[0] 

def gnuplot_GifTest(): 
    commands = [\ 
     "set datafile separator ','",\ 
     "set terminal gif",\ 
     "set output",\ 
     "plot '-' using 1:2 with linespoints, '' using 1:2 with linespoints",\ 
     ] 
    data = [\ 
     "1,1",\ 
     "2,2",\ 
     "3,5",\ 
     "4,2",\ 
     "5,1",\ 
     "e",\ 
     "1,5",\ 
     "2,4",\ 
     "3,1",\ 
     "4,4",\ 
     "5,5",\ 
     "e",\ 
     ] 
    return (commands, data) 

if __name__=="__main__": 
    (commands, data) = gnuplot_GifTest() 
    print gnuplot(commands, data) 
1

Voici un autre exemple qui étend certaines des réponses précédentes. Cette solution nécessite Gnuplot 5.1 car elle utilise des datablocks. Pour plus d'informations sur les blocs de données, exécutez help datablocks dans gnuplot. Le problème avec certaines des approches précédentes est que plot '-' consomme instantanément les données qui suivent immédiatement la commande plot. Il n'est pas possible de réutiliser les mêmes données dans une commande plot suivante. Les datablocks peuvent être utilisés pour résoudre ce problème. En utilisant des datablocks, nous pouvons imiter plusieurs fichiers de données. Par exemple, vous pouvez tracer un graphique en utilisant les données de deux fichiers de données, par ex. plot "myData.dat" using 1:2 with linespoints, '' using 1:3 with linespoints, "myData2.dat" using 1:2 with linespoints. Nous pourrions envoyer ces données directement à gnuplot sans avoir besoin de créer de véritables fichiers de données.

import sys, subprocess 
from os import linesep as nl 
from subprocess import Popen, PIPE 


def gnuplot(commands, data):              
    """ drive gnuplot, expects lists, returns stdout as string """ 
    script= nl.join(data)+nl.join(commands)+nl 
    print script 
    args = ["gnuplot", "-p"] 
    p = Popen(args, shell=False, stdin=PIPE)      
    return p.communicate(script)[0] 

def buildGraph(): 
    commands = [\ 
     "set datafile separator ','",\ 
     "plot '$data1' using 1:2 with linespoints, '' using 1:3 with linespoints, '$data2' using 1:2 with linespoints",\ 
     ] 
    data = [\ 
     "$data1 << EOD",\ 
     "1,30,12",\ 
     "2,40,15",\ 
     "3,35,20",\ 
     "4,60,21",\ 
     "5,50,30",\ 
     "EOD",\ 
     "$data2 << EOD",\ 
     "1,20",\ 
     "2,40",\ 
     "3,40",\ 
     "4,50",\ 
     "5,60",\ 
     "EOD",\ 
     ] 

    return (commands, data) 


def main(args): 
    (commands, data) = buildGraph() 
    print gnuplot(commands, data) 


if __name__ == "__main__": 
    main(sys.argv[1:]) 

Cette méthode est un peu plus polyvalent que plot '-' car il facilite la réutilisation des mêmes données à plusieurs reprises, y compris sur la même commande plot: https://stackoverflow.com/a/33064402/895245 Notez que cette approche exige que les données sont transmises à gnuplot avant les commandes de tracé!

Aussi, je ne l'ai pas utiliser IOString comme @ppetraki a fait, puisque, apparemment, cela est plus lent qu'un simple menuisier de liste: https://waymoot.org/home/python_string/

+0

Les valeurs dans les données doivent être écrites sans comas. Les colonnes sont séparées par des espaces, comme indiqué dans l'aide de gnuplot. Toujours dans python3 vous devez encoder() les scripts de chaînes: 'return p.communicate (script.encoder ('utf-8')) [0] ' –

+0

@terencehill Ce n'est pas vrai: vous pouvez utiliser n'importe quel séparateur, à condition de spécifier la commande' set separator ',' ', voir mon exemple ci-dessus. –

+0

Désolé, j'ai changé la commande et j'ai raté la ligne se référant au séparateur. –