2010-01-29 24 views
16

Tengo un script de python que después de algunos cálculos generará dos archivos de datos formateados como entrada gnuplot.Llamar a gnuplot desde python

¿Cómo puedo 'llamar' gnuplot desde python?

Quiero enviar la siguiente cadena pitón como entrada a gnuplot:

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

donde 'eout' y 'Nout' son los dos nombres de archivo.

PS: I prefiero no utilizar módulos de Python adicionales (por ejemplo gnuplot-Py.), Sólo el API estándar.

Gracias

+0

que desea llamar usando gnuplot API (que se encuentra en C, por lo que tendría que escribir algún código de pegamento como el de gnuplot-py) o simplemente ejecuta "gnuplot" en shell? –

+0

Simplemente ejecuta gnuplot en shell. –

Respuesta

6

Un enfoque simple podría ser simplemente escribir un tercer archivo que contiene los comandos de gnuplot y luego decirle a Python para ejecutar gnuplot con eso archivo. Supongamos que escribe

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

en un archivo denominado tmp.gp. A continuación, puede utilizar

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

Gracias, este truco finalmente funcionó. Una mención: system ('gnuplot -persist tmp.gp') para persistir las ventanas después de que termina el script. –

22

El módulo subprocess le permite llamar a otros programas:

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

Después de leer su ejemplo, he escrito una función similar, desafortunadamente, no hay resultados. (POpen = Popen, un error tipográfico, creo, pero este no era el problema) –

+1

Sí, POpen era un error tipográfico. Aparte de eso, tal vez necesites especificar la ruta completa a gnuplot o agregar el interruptor '-persist' que mencionas en otro comentario. También puede verificar 'plot.returncode' para ver si hay errores. – sth

14

subproceso se explica muy claramente en Doug Hellemann de Python Module of the Week

Esto funciona así:

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 

También se podría utilizar 'comunicar', pero la ventana gráfica se cierra inmediatamente, a menos se usa un comando de pausa gnuplot

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

Use 'shell = False' y gnuplot' --persist' si solo quiere mostrar la ventana, y no tiene Python, ciérrela ... – sdaau

4

Estaba tratando de hacer algo similar, pero adicionalmente quería alimentar datos desde python y generar el archivo como variable (por lo que ni los datos ni el gráfico son archivos reales). Esto es lo que ocurrió:

#! /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()) 

Ese guión vertederos de la gráfica a la salida estándar como el último paso en principal. La línea de comandos equivalente (donde el gráfico se canaliza a 'out.gif') sería:

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

Aquí es una clase que proporciona una interfaz para 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

estoy un poco tarde, pero dado que me tomó algo de tiempo hacer que funcione, tal vez vale la pena poner una nota. Los programas están trabajando con Python 3.3.2 en Windows.

Observe que los bytes se usan en todas partes, no en cadenas (p.b "trama x", no simplemente "trama x"), pero en caso de que sea un problema, sólo tiene que hacer algo como:

"plot x".encode("ascii") 

Primera solución: usar comunican para enviar todo, y se cierran cuando se hace. No se debe olvidar pause, o la ventana se cierra de inmediato. Sin embargo, no es un problema si gnuplot se usa para almacenar imágenes en archivos.

from subprocess import * 
path = "C:\\app\\gnuplot\\bin\\gnuplot" 
p = Popen([path], stdin=PIPE, stdout=PIPE) 
p.communicate(b"splot x*y\npause 4\n") 

Segunda solución: enviar comandos uno tras otro, usando stdin.write (...). ¡Pero, no se olvide de enjuague! (esto es lo que no entendí bien al principio) Y use finalice para cerrar la conexión y gnuplot cuando finalice el trabajo.

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

Fui con la sugerencia de Ben como yo estaba calculando tablas de un trabajo de apio y encontraron que sería bloqueará cuando se lee de salida estándar. Lo rediseñé así utilizando StringIO para crear el archivo destinado a stdin y subprocess.communicate para obtener el resultado de inmediato a través de stdout, no se requiere lectura.


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

Aquí hay otro ejemplo que amplía algunas de las respuestas anteriores. Esta solución requiere Gnuplot 5.1 porque usa bloques de datos. Para obtener más información sobre bloques de datos, ejecute help datablocks en gnuplot. El problema con algunos de los enfoques anteriores es que plot '-' consume instantáneamente los datos que siguen inmediatamente al comando de trazado. No es posible reutilizar los mismos datos en un comando de trazado posterior. los bloques de datos se pueden usar para aliviar este problema. Usando bloques de datos podemos imitar múltiples archivos de datos. Por ejemplo, es posible que desee trazar un gráfico con los datos de dos archivos de datos, p. plot "myData.dat" using 1:2 with linespoints, '' using 1:3 with linespoints, "myData2.dat" using 1:2 with linespoints. Podríamos suministrar estos datos directamente a gnuplot sin la necesidad de crear archivos de datos reales.

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:]) 

Este método es un poco más versátil que plot '-' ya que facilita la reutilización de los mismos datos varias veces, incluyendo en el mismo comando plot: https://stackoverflow.com/a/33064402/895245 Tenga en cuenta que este enfoque requiere que los datos se alimenta a gnuplot antes de los comandos de la trama!

Además, no utilizar IOString como @ppetraki lo hicieron, ya que al parecer esto es más lento que una simple lista de carpintero: https://waymoot.org/home/python_string/

+0

Los valores en los datos se deben escribir sin comas. Las columnas están separadas por espacios, como se informa en la ayuda de gnuplot. También en python3 debe codificar() los scripts de cadena: 'return p.communicate (script.encode ('utf-8')) [0] ' –

+0

@terencehill Eso no es cierto: puede usar cualquier separador, siempre y cuando especifique el comando' set datafile separator ',' ', vea mi ejemplo anterior. –

+0

Lo siento, cambié el comando y omití la línea que hace referencia al separador. –