Подтвердить что ты не робот

Вызов gnuplot из python

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

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

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

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

где 'eout' и 'nout' - это два имени файла.

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

Спасибо

4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

Подпроцесс объясняется очень четко на Дуге Хеллемане Модуль 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
""")

Ответ 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())

Этот script сбрасывает граф в stdout как последний шаг в 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

Ответ 5

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

Ответ 6

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

Ответ 7

Я пошел с предложением Бена, когда я вычислял диаграммы из задания на сельдерей и обнаружил, что он будет блокироваться при чтении из 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)

Ответ 8

Вот еще один пример, который расширяет некоторые из предыдущих ответов. Для этого решения требуется Gnuplot 5.1, поскольку он использует блоки данных. Для получения дополнительной информации о блоках данных выполните help datablocks в gnuplot. Проблема с некоторыми из предыдущих подходов заключается в том, что plot '-' мгновенно потребляет данные, которые сразу же следует за командой 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 '-', поскольку он упрощает повторное использование одних и тех же данных несколько раз, в том числе по той же команде: fooobar.com/info/205386/... Обратите внимание, что этот подход требует, чтобы данные передавались в gnuplot перед командами графика!

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