在纯python环境中使用processing的实时画图功能
processing的实时画图功能是很强大的,他提供了最便捷简洁的画图函数,是强大的可视化工具。但是这样的工具也是存在问题的,那就是无法在一般的python环境中使用processing。经过了各种探索,我终于找到了在本地最便捷的从一般python环境中调用processing进行动态可视化的方法,那就在一般的python程序中通过本地网络连接到processing:
具体的说,就是先启动一个processing程序,然后再启动python程序,让两者通过本地网络连接进行通讯,从而使得python程序能够给processing程序发指令。
程序的复杂的代码逻辑都在python中,而不是在processing中,从而使得我们的方案能够被广大的python使用者所使用。processing中的程序体量小,而且是设计好的通用性程序,使得使用者基本不必对其进行更改,从而变得更加方便。最终将processing丰富高效的可视化能力与python的丰富的科学计算生态以及强大的IDE环境的长处共同发挥出来。
在本文中,我们以一个有趣的例子为例,介绍了我们的processing画图器,以及如何在python程序中使用它。而且,整个项目已经有了对应的Git仓库。
本文的内容包括:画图器使用例子:正态分布抽奖机展示
如何启动processing画图器
Python中如何调用画图器-----发指令前的准备工作:先将python连接到画图器程序
-----rpc指令格式
-----rpc指令的批量发送在普通python环境中使用画图器实现“抽奖机”
画图器方案内部细节展示
画图器使用例子:正态分布抽奖机展示
看一下程序效果:可视化方案的效果验证https://www.zhihu.com/video/1167184987182358528
可以看出,在每一帧的指令数达到几十条几百条的时候,可视化图像依旧非常流畅,验证了我们方案的可行性。该程序的具体功能是这样的:从三角堆的顶部持续产生下降的小球。
小球在三角堆中从上一行的某个位置(该行白点中的某个白点的位置)走到下一行的两个相邻位置中的一个,且走到下行两个位置各自的概况相同。
小球离开三角堆后迅速降落到底部。
画面底部还有一个柱形图统计落到每个位置的小球的累计数量。由概率论的知识可以知道,底部的统计条形图基本是依照正态分布而分布的。
(之所以在小标题上加了一个问号❓,是因为不知道这个装置的学名(正式的,惯用的名字)是什么。)
如何启动processing画图器
方法1:
下载python,安装python模式,然后将画图器代码复制到新建立的processingpy工程中,然后点击processing编辑器的启动按钮,从而运行。
方法2:
在下面的路径中,有作者已经上传的打包好的win 程序。在windows系统中下载整个文件夹,然后双击exe文件即可启动画图器。https://github.com/shiyunlai233/Processing_Ploter_for_python/tree/master/plotergithub.com
Python中如何调用画图器
发指令前的准备工作:先将python连接到画图器程序
python程序和画图器之间的连接方式是:在本地网络连接的基础上,进行远程过程调用(RPC)。所以我们先使用python中内置的socket库,连接到画图器的地址:127.0.0.1:12345。然后将下面的RPC类进行实例化,得到rpc,就可以通过对rpc进行操作,实现对于画图器的控制了。具体到我们的程序中,就是说在python程序中要想控制画图器,就需要先启动画图器,并在python中运行以下代码片段:
import socket, json, pickle
class RPCProxy:
def __init__(self, connection):
self._connection = connection
self.data = []
def __getattr__(self, name):
def do_rpc(*args, **kwargs):
self.data.append(json.dumps((name, args, kwargs)))
return do_rpc
def send_all(self):
self._connection.send(pickle.dumps(self.data, protocol=0))
self.data.clear()
address = ('127.0.0.1', 12345)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(address) # 连接到画图器
rpc = RPCProxy(s) # 得到了rpc
rpc指令格式
画图端程序的画图指令函数用法,和processing中的编程命令是非常类似的,只是在每个函数的前面加了一个 “rpc.” 比如原先在processing中的圆指令就从circle()变成了rpc.circle()。比如说上面的画图程序就来自processing官网的一个入门教程。对应的在processing中的画图命令是这样的:
# python中给画图器发出的命令
rpc.rect(100,100,20,100)
rpc.ellipse(100,70,60,60)
rpc.ellipse(81,70,16,32)
rpc.ellipse(119,70,16,32)
rpc.line(90,150,80,160)
rpc.line(110,150,120,160)
# 效果相同的processing程序代码
rect(100,100,20,100);
ellipse(100,70,60,60);
ellipse(81,70,16,32);
ellipse(119,70,16,32);
line(90,150,80,160);
line(110,150,120,160);
可用的画图命令包括:arc, circle, ellipse, line, point, quad, rect, square, triangle, fill, stroke, background, 这些命令的详细用法和在processing中是一致的,可以参考:processing reference。在本文程序中,只用到了画圆指令circle和画方指令rect。
rpc指令的批量发送
为了高效而准确地利用画图器指令,我们还需要一个额外的rpc指令rpc.send_all()。在执行这条命令后,会将之前的rpc指令统一发送给画图器,并清空python中的rpc指令缓存。
建议最好每发送完对于一帧的画图指令后,再进行rpc.send_all()。因为这样就能实现画面流畅度的最大化。
所以现在python程序中发出可视化指令的框架是这样的:
for i in rane(100):
rpc.background(0) # 画背景(在最后一行代码处发送)
rpc.circle() # 画原指令(在最后一行代码处发送)
rpc.rect() # 其他画图指令(在最后一行代码处发送)
rpc.send_all() # 发出这一帧对应的所有指令
使用画图器实现“抽奖机”的python代码
在启动画图器后,在python环境中运行画图器代码,就能够获得我们上面视频中的效果了。
想要观看“抽奖机”完整代码可以前往对应Git仓库:https://github.com/shiyunlai233/Processing_Ploter_for_python/blob/master/examples/Normally_distributed_lottery_machine.pygithub.com
做了一个画图器的完整使用视频:https://www.zhihu.com/video/1184931701171638272
画图器方案内部细节展示
1 编码方式的改进:
这次的编码使用了: 指令 → json编码 →多条指令组成list → 对list进行pickle编码 → 发送。而在processing中的解码就是与之相反的流程。
这样的编码方式和上次的实现相比有哪些优点:1,指令之间不再需要插入间隔符。2,指令在传输过程中使用的是pickle编码的结果,而不是json编码的结果,效率更高。
2 传输方式的改进:
上次的方法是逐条命令传递,逐条命令执行。这样产生的问题是,由于在python程序中对同一帧画面发出指令的间隔可能会比较大,导致可视化画面闪烁,或者有其他不好的现象。
所以改进成了先将同一帧的指令组装成一个list,然后对这一帧的所有指令统一发送,统一执行的方法,从而实现了画图器方案的优化。