2010-01-29 5 views
16

У меня есть скрипт python, который после некоторых вычислений будет генерировать два файла данных, отформатированных как вход gnuplot.Вызов gnuplot из python

Как мне вызвать gnuplot из python?

Я хочу послать следующую питона строку в качестве входных данных GNUPLOT:

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

где 'Eout' и 'ноута' являются два именами файлов.

PS: Я предпочитаю не использовать дополнительный питон модули (например, Gnuplot-PY.), Только стандартный API.

Спасибо

+0

Вы хотите позвонить Gnuplot с помощью API (который находится в C, поэтому вы должны написать некоторый код клея, подобный тому, который находится в gnuplot-py) или просто выполнить «gnuplot» в оболочке? –

+0

Просто выполните gnuplot в оболочке. –

ответ

6

Простой подход может быть просто написать третий файл, содержащий ваши команды GNUPLOT, а затем сказать, Python выполнить GNUPLOT с этим файл. Скажем, вы пишете

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

в файл под названием tmp.gp. Затем вы можете использовать

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

Спасибо, этот хак в конечном итоге сработал. Одно упоминание: system ('gnuplot -persist tmp.gp'), чтобы сохранить окна после завершения скрипта. –

22

Модуль subprocess позволяет вызывать другие программы:

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

После прочтения вашего примера, я написал аналогичную функцию, к сожалению, никаких результатов. (POpen = Popen, опечатка, я считаю, но это не проблема) –

+1

Да, POpen была опечаткой. Помимо этого, возможно, вам нужно указать полный путь к gnuplot или добавить переключатель «-persist», который вы укажете в другом комментарии. Вы также можете проверить «plot.returncode» на наличие ошибок. – sth

14

Subprocess объясняется очень ясно на Doug Hellemann-х Python Module of the Week

Это хорошо работает:

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 

можно было бы использовать «общаться», но окно участок закрывается немедленно, если используется команда паузы gnuplot

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

Используйте 'shell = False' и gnuplot' --persist', если вы просто хотите показать это окно, а Python не закрывать его ... – sdaau

4

Я пытался сделать что-то подобное, но, кроме того, мне хотелось подавать данные изнутри python и выводить в качестве переменной файл файл (поэтому ни данные, ни график не являются фактическими файлами). Это то, что я придумал:

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

Этот сценарий выводит график на стандартный вывод в качестве последнего шага в главного. Эквивалент командной строки (где граф перенаправляются «out.gif») будет:

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

Вот класс, который предоставляет интерфейс для 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

Я немного поздно, но поскольку мне потребовалось некоторое время, чтобы заставить его работать, возможно, стоит поместить заметку. Программы работают с Python 3.3.2 в Windows.

Обратите внимание, что байты используются повсюду, а не строки (например,б «участок х», а не просто «участок х»), но в случае, если это проблема, просто сделать что-то вроде:

"plot x".encode("ascii") 

Первое решение: использовать общаться отправить все, и закрываются, когда это делается. Не следует забывать pause, или окно закрывается сразу. Однако это не проблема, если gnuplot используется для хранения изображений в файлах.

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

Второе решение: отправлять команды один за другим, используя stdin.write (...). Но, не забудьте промыть! (сначала это не получилось) И используйте завершение, чтобы закрыть соединение и gnuplot, когда работа выполнена.

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

Я пошел с предложением Бен, как я вычисление диаграммы с работой сельдерея и обнаружил, что он будет зависнуть при чтении стандартного вывода. Я переработал его так, используя StringIO, чтобы создать файл, предназначенный для stdin и subprocess.communicate, чтобы получить результат сразу через stdout, без необходимости чтения.


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

Вот еще один пример, который охватывает некоторые из предыдущих ответов. Это решение требует Gnuplot 5.1, потому что он использует блоки данных. Для получения дополнительной информации о блоках данных выполните help datablocks в gnuplot. Проблема с некоторыми из предыдущих подходов заключается в том, что plot '-' мгновенно потребляет данные, которые сразу же следует за графиком. Невозможно повторно использовать одни и те же данные в следующей команде plot. Блоки данных могут использоваться для устранения этой проблемы. Используя блоки данных, мы можем имитировать несколько файлов данных. Например, вы можете построить график, используя данные из двух файлов данных, например. plot "myData.dat" using 1:2 with linespoints, '' using 1:3 with linespoints, "myData2.dat" using 1:2 with linespoints. Мы могли бы передавать эти данные непосредственно в gnuplot без необходимости создавать фактические файлы данных.

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

Этот метод является немного более универсален, чем plot '-', как это облегчает повторное использование несколько раз же данные, в том числе и на одной и той же команды участка: https://stackoverflow.com/a/33064402/895245 Обратите внимание, что этот подход требует, чтобы данные подаются в gnuplot перед сюжетными командами!

Кроме того, я не использовал IOString, как @ppetraki сделал, так как, по-видимому, это медленнее, чем простой список столяра: https://waymoot.org/home/python_string/

+0

Значения в данных должны быть записаны без комы. Столбцы разделены пробелами, о чем сообщается в справке gnuplot. Также в python3 вы должны закодировать() строковые скрипты: 'return p.communicate (script.encode ('utf-8')) [0] ' –

+0

@terencehill Это неверно: вы можете использовать любой разделитель, если вы укажете команду' set datafile separator ',' ', см. мой пример выше. –

+0

Извините, я сменил команду и пропустил строку, ссылающуюся на разделитель. –