Cython(14) Julia集合を動画にする


2022年 10月 27日

1枚の1000x1000のJulia集合の画像作成が0.02秒余りでできるようになったので、定数Cを次々に変更すると、動画になると思われるので、やってみよう。
自分で画像を次々と作っては表示するという仕組みを作るのは面倒なので、PythonのGUIを利用することにしよう。

色々なGUIが用意されているが、ここではPythonに標準でついてくるTkinterというのを使うことにしよう。
今更Tkinterなのだが、PythonのGUIは種類がとても多く選ぶのに困る状態である。
そのため、Python標準のTkinterという安直この上ない選択をした。
ここでは凝ったGUIを作る予定はないので、あえて標準の古臭いTkinterにした。
実際にGUIを作る場合には、適切なGUIを選ぼう。

CythonとPythonに分割

その前に、Julia集合を作成・テストに使っていたプログラムjulia_cy5.pyxを2つに分割する。

  1. 高速な計算が必要な部分でCythonにする
  2. GUI部分で速度が不要の部分は、プログラミングが楽なPythonにする

まず、Cython部分を示す。

# distutils: extra_compile_args = -fopenmp
# distutils: extra_link_args = -fopenmp

# cython: boundscheck=False
# cython: wraparound=False

from threading import Thread
from cython.parallel import *
import numpy as np
import matplotlib.pyplot as plt
import time

cdef int escape( double complex z, double complex c,
        	double z_max, int n_max) nogil:
	cdef:
    	int i = 0
    	double z_max2 = z_max * z_max
   	 
	while norm2(z) < z_max2 and i < n_max:
    	z = z * z + c
    	i += 1
	return i

cdef inline double norm2(double complex z) nogil:
	return z.real * z.real + z.imag * z.imag

def calc_julia( int resolution, double complex c,
            	double bound=1.2, double z_max=2.0, int n_max=1000 ):
	cdef:
    	double step = 2.0 * bound / resolution
    	int i,j
    	double complex z
    	double real, imag
    	int[:,:] counts
	counts = np.zeros((resolution+1,resolution+1), dtype=np.int32)

	for i in prange(resolution+1,nogil=True,schedule='static',chunksize=1):
    	imag = bound - i * step
    	for j in range(resolution+1):
        	real = -bound + j * step
        	z = real + imag *1j
        	counts[i,j] = escape(z,c,z_max,n_max)

	return np.asarray(counts)

calc_julia以下の部分をそのまま切り出しただけなので、説明は不要だろう。

残り部分を、Pythonのままで、ファイル名をjulia_move0.py とした。

## ジュリア集合を動かす

from threading import Thread
from cython.parallel import *
import numpy as np
import matplotlib.pyplot as plt
import time

from calc_julia import calc_julia

def julia():
	tm_start = time.time()
	jl = calc_julia(1000,(0.322+0.047j))
	tm_end = time.time()

	plt.figure(figsize=(10,10))
	plt.imshow(np.log(jl
	print("time={}".format(tm_end-tm_start))
	plt.show()
    
if __name__ == '__main__':
	julia()

これで、コマンドベースで直接起動することができる。

$ python3 julia_move0.py
time=0.024521589279174805

julia集合の画像も出てくるが、まったく同じものなので省略する。

TkInter静止画版

Python部分をTkInter対応に書き換えてみよう。とりあえずは、1枚の画像を表示した後はメインループを回っているが、イベントの発生を何も書かなければ、最初の画像を表示し続けるだけである。

書き換えはPythonの部分だけである。
GUIの書き方の基本が分かっていれば、理解は簡単ではないかと思う。画像データをcanvasに貼り付ける部分の処理が多くなっているが、簡単なコメントをステップ毎に付けた。
途中でそのまま画像ファイルとしてセーブできるところがあり、今はコメントにしている。このコメントを外せば、画像ファイルが作られる。

PythonのGUIが初めての場合には、何らかの資料、本などで少し勉強した方が良いかもしれない。

## julia集合の画像をtkinterのcanvasに描く, 動的に変化する。

import calc_julia
import tkinter
from PIL import Image, ImageTk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
    
def draw_new_image():
	global C_value, canvas, im

	im = calc_julia.calc_julia(1000,C_value)	## Julia集合の計算

	im = np.uint8(plt.get_cmap()(im)*255)   	## defaultのカラーマップを適用し、uint8 に変換
	im = Image.fromarray(im)                	## <class 'PIL.Image.Image'>
	## im.save('julia_move1.png')           	## 画像ファイル(png)としてセーブできる
	im = ImageTk.PhotoImage(image=im)       	## Tkinterの標準のフォトイメージ
	canvas.create_image( 500, 500, image=im )   ## Canvasにイメージを貼り付ける。(500,500)が中心
	canvas.update()                         	## update()されると表示される

def gui_init():
	global canvas, root

	root = tkinter.Tk()
	root.wm_title("Julia集合をtkinterのcanvasに動的に表示")

	canvas_frame = tkinter.Frame(root)                          	## frameを作る
	canvas_frame.pack()                                         	## packする
	canvas = tkinter.Canvas(canvas_frame,width=1001,height=1001)	## frameの中にcanvasを作る
	canvas.pack(expand = tkinter.YES, fill = tkinter.BOTH)      	## packして配置が決定

if __name__ == '__main__':
	gui_init()                              	## GUIの初期化
	C_value = complex(0.322,0.047)          	## 初期定数C
	draw_new_image()                        	## 初期表示   
	root.mainloop()                         	## メインループ(イベント待ち)

実行すると、ちゃんと同じ画像が出てくることが確かめられる。

ここまでできれば、動画にするのはあと一歩であるが、それは次回に回す。