見出し画像

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にも少し慣れてきましたが、まだまだ知らないことだらけなので引き続き弄っていこうと思います。根気が続く限り…….

外見や機能は特に変わっていません。

いいなと思ったら応援しよう!