Вызов gnuplot из python - PullRequest
       52

Вызов gnuplot из python

19 голосов
/ 29 января 2010

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

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

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

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

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

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

Спасибо

Ответы [ 8 ]

23 голосов
/ 29 января 2010

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

import subprocess
plot = subprocess.Popen(['gnuplot'], stdin=subprocess.PIPE)
plot.communicate("plot '%s' with lines, '%s' with points;" % (eout,nout))
16 голосов
/ 15 февраля 2011

Подпроцесс объяснен очень ясно на Дуга Хеллемана Модуль Python недели

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

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
""")
7 голосов
/ 29 января 2010

Простой подход может состоять в том, чтобы просто написать третий файл, содержащий ваши команды 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')
5 голосов
/ 22 апреля 2011

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

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

Этот скрипт выводит график на стандартный вывод в качестве последнего шага в main . Эквивалентная командная строка (где график передан в 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
3 голосов
/ 07 февраля 2014

Я согласился с предложением Бена, когда вычислял графики из сельдерея, и обнаружил, что он зависает при чтении из stdout. Я изменил его так, используя 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)
2 голосов
/ 07 августа 2013

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

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

"plot x".encode("ascii")

Первое решение: используйте сообщить , чтобы отправить все, и закройте, когда это будет сделано. Нельзя забывать пауза , иначе окно закроется сразу. Тем не менее, это не проблема, если 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 (...). Но, не забудь флеш ! (это то, что я сначала не понял). И используйте terminate , чтобы закрыть соединение и 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 голосов
/ 15 июня 2011

Вот класс, который предоставляет интерфейс для 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)
1 голос
/ 27 июня 2017

Вот еще один пример, который расширяет некоторые из предыдущих ответов. Это решение требует 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 перед командами plot!

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

...