Python tkinterを使ってOpenAIで画像を生成させるプログラム②
前回のコードは単一のpyファイルで作成しておりClassとしてはUIに相当するApplicationクラスしか作っていませんでした。
あれからよくよく考えるとOpenAIとのやりとりを仲介するコントローラークラスがあると良いなと思い始めたのでOpenAIControllerクラスを作成して別ファイルにすることにしました。
とりあえずそちらのコードを
# oacont.py
import openai
import base64
from io import BytesIO
class OpenAIController:
def __init__(self, apiKey):
openai.api_key = apiKey
def __generateRequestWord(self, word):
requestWord = "次の原文を前書きや挨拶など余分な出力なしで、画像生成AIに素晴らしい画像を出力させるための指示文章を英語で出力してください。原文「"
requestWord = requestWord + word + "」"
try:
completion = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": requestWord}]
)
return completion.choices[0].message.content
except openai.error.RateLimitError as e: raise
def GenerateImages(self, word, numberOfImages):
results = {}
try:
requestWord = self.__generateRequestWord(word)
response = openai.Image.create(
prompt = requestWord,
n = numberOfImages,
size = "1024x1024",
response_format = "b64_json",
)
for data, n in zip(response["data"], range(numberOfImages)):
img_data = base64.b64decode(data["b64_json"])
results[n] = BytesIO(img_data)
return results
except openai.error.RateLimitError as e: raise OpenAIError(f"OpenAI API request exceeded rate limit: {e}")
class OpenAIError(Exception):
pass
コンストラクタでAPIキーを引き渡して利用するクラスになっています。画像を取得することだけを想定しているためパブリックなメソッドはGenerateImagesだけにしました。引数は生成呪文(AIに作らせたい画像の説明文)と作成して欲しい画像の枚数、戻り値は生成されたイメージのバイナリデータ(BytesIO)のディクショナリーです。前回はOpenAIから取得した生成画像をファイルとして保存していましたが不要なのでメモリー上だけでデータを利用することにしました。
日本語の生成呪文を英語に翻訳させる__generateRequestWordメソッドはプライベート扱いにしています。
OpenAIの試用中は短い間隔で利用するとエラーが帰ってきて1分待つように促されます。これを外部のクラスに伝えるため新たにOpenAIErrorクラス(例外クラス)を用意しました。
次のコードはGUIを担当するoagui.pyです。
# oagui.py
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
from oacont import OpenAIController, OpenAIError
OPENAI_API_KEY = "あなたのAPIキーはここどすえ"
class Application(tk.Frame):
NUMBER_OF_IMAGES = 4
CANVAS_BASE_X = 22
CANVAS_BASE_Y = 120
CANVAS_EXT_X = 440
CANVAS_EXT_Y = 536
cans = {}
def __init__(self,master = None):
super().__init__(master)
self.pack()
master.geometry("860x960")
master.title("OpenAiで画像生成しちゃうよん")
self.oac = OpenAIController(OPENAI_API_KEY)
self.__create_controls()
def __create_canvases(self):
i = 0
while i < self.NUMBER_OF_IMAGES:
can = tk.Canvas(self.master, bg="white", width=400, height=400)
if i == 0: can.place(x=self.CANVAS_BASE_X, y=self.CANVAS_BASE_Y)
elif i == 1: can.place(x=self.CANVAS_EXT_X, y=self.CANVAS_BASE_Y)
elif i == 2: can.place(x=self.CANVAS_BASE_X, y=self.CANVAS_EXT_Y)
else: can.place(x=self.CANVAS_EXT_X, y=self.CANVAS_EXT_Y)
self.cans[i] = can
i += 1
def __create_controls(self):
tk.Label(text="生成呪文").place(x=20, y=20)
self.tbKeyword = tk.Text(width=92, height=6)
self.tbKeyword.place(x=90, y=22)
btnA = tk.Button(self.master, text="生成しちゃうぞ!", command=self.__btn_Click, width=12, height=5)
btnA.place(x=750, y=18)
self.__create_canvases()
def __btn_Click(self):
images = {}
wd = self.tbKeyword.get("1.0", "end")
if (len(wd) == 1): return
try:
images = self.oac.GenerateImages(wd, self.NUMBER_OF_IMAGES)
except OpenAIError as e:
messagebox.showerror("エラーっす", e)
return
self.__showGeneratedImages(images)
def __showGeneratedImages(self, images):
i = 0
while i < self.NUMBER_OF_IMAGES:
img = Image.open(images[i])
img = img.resize((400, 400))
self.cans[i].photo = ImageTk.PhotoImage(img)
self.cans[i].create_image(0, 0, image=self.cans[i].photo, anchor=tk.NW)
i += 1
def main():
win = tk.Tk()
app = Application(master=win)
app.mainloop()
if __name__ == "__main__":
main()
openaiを直接インポートしていないことがわかります。
4行目で別ファイルのoacont.pyからOpenAIControllerとOpenAIErrorクラスをインポートしています。正しく読み込むためには2つのpyファイルを同一フォルダ内に配置する必要があります。
btn_ClickイベントメソッドでOpenAIController.GenerateImagesメソッドを呼び出しています。もしエラーになったらOpenAIErrorが発生するのでエラーがあったことをユーザーに伝えるためメッセージボックを表示しています。
__showGeneratedImagesメソッドにはOpenAIController.GenerateImagesメソッドで取得したディクショナリーを引き渡します。ファイルから直接オープンするのではなくバイナリデータから画像を取得しています。
今回のまとめ
クラスごとにファイルを分けることによりコードがすっきりしたと思います。それ以外のメリットとしてはOpenAIControllerクラスは他のプログラムで再利用可能となっている点は見逃せません。
クラスのプライベートメソッドはメソッド名の先頭をアンダースコアから始めれば良いことが分かりました。
public > create_controlls
private > __create_controlls
Pythonにも少し慣れてきましたが、まだまだ知らないことだらけなので引き続き弄っていこうと思います。根気が続く限り…….