【问题标题】:Calling gnuplot from python从 python 调用 gnuplot
【发布时间】:2010-01-29 12:44:00
【问题描述】:

我有一个 python 脚本,经过一些计算后会生成两个格式化为 gnuplot 输入的数据文件。

如何从 python 中“调用”gnuplot?

我想将以下 python 字符串作为输入发送到 gnuplot:

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

其中 'eout' 和 'nout' 是两个文件名。

PS: 我宁愿不使用额外的 python 模块(例如 gnuplot-py),只使用标准 API。

谢谢

【问题讨论】:

  • 你想使用 API 调用 gnuplot(这是在 C 中,所以你必须编写一些像 gnuplot-py 中的胶水代码)或者只是在 shell 中执行“gnuplot”?
  • 只需在 shell 中执行 gnuplot。

标签: python gnuplot


【解决方案1】:

subprocess 模块让您可以调用其他程序:

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

【讨论】:

  • 看完你的例子,我写了一个类似的函数,可惜没有结果。 (POpen = Popen,我相信是一个错字,但这不是问题)
  • 是的,POpen 是一个错字。除此之外,也许您需要指定 gnuplot 的完整路径或添加您在另一条评论中提到的“-persist”开关。您还可以检查plot.returncode 是否有错误。
  • 对于收到TypeError: a bytes-like object is required, not 'str'” 错误的人:encoding
【解决方案2】:

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 pause 命令

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

【讨论】:

  • 如果你只想显示窗口而不是让 Python 关闭它,请使用 shell=False 和 gnuplot --persist...
【解决方案3】:

一种简单的方法可能是只编写包含 gnuplot 命令的第三个文件,然后告诉 Python 使用该文件执行 gnuplot。说你写

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

到一个名为 tmp.gp 的文件。然后就可以使用了

from os import system, remove
system('gnuplot -persist tmp.gp')
remove('tmp.gp')

【讨论】:

  • 谢谢,这个 hack 最终奏效了。一提: system('gnuplot -persist tmp.gp') 以便在脚本完成后保留窗口。
【解决方案4】:

我试图做类似的事情,但另外我想从 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())

该脚本将图形转储到标准输出,作为 ma​​in 中的最后一步。等效的命令行(图形通过管道传输到“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】:

    我接受了 Ben 的建议,因为我正在计算芹菜作业中的图表,发现从标准输出读取时它会锁定。我重新设计了它,使用 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)
    

    【讨论】:

      【解决方案6】:

      这是一个为 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)
      

      【讨论】:

        【解决方案7】:

        我有点晚了,但由于我花了一些时间才使它起作用,也许值得记一下。这些程序在 Windows 上使用 Python 3.3.2。

        请注意,字节在任何地方都使用,而不是字符串(例如 b“plot x”,而不仅仅是“plot x”),但如果出现问题,只需执行以下操作:

        "plot x".encode("ascii")
        

        第一个解决方案:使用 communicate 发送所有内容,并在完成后关闭。千万不要忘记暂停,否则窗口会立即关闭。但是,如果使用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()
        

        【讨论】:

          【解决方案8】:

          这是扩展之前一些答案的另一个示例。此解决方案需要 Gnuplot 5.1,因为它使用数据块。有关数据块的更多信息,请在 gnuplot 中执行 help datablocks。 以前的一些方法的问题是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!

          另外,我没有像@ppetraki 那样使用 IOString,因为显然这比简单的列表连接器要慢:https://waymoot.org/home/python_string/

          【讨论】:

          • data中的值必须不带逗号。列由空格分隔,如 gnuplot 帮助中所述。同样在 python3 中,您必须 encode() 字符串脚本:return p.communicate(script.encode('utf-8'))[0]
          • @terencehill 这不是真的:你可以使用任何分隔符,只要你指定命令set datafile separator ',',看我上面的例子。
          • 对不起,我更改了命令,错过了引用分隔符的行。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-07-23
          • 1970-01-01
          • 1970-01-01
          • 2018-05-08
          • 2015-05-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多