# -*- coding: utf-8 -*-
##--------------------------------------------------
## Stable Diffusion with diffusers(042) Ver 0.01
## GUI interface
## 2025.06.17 Masahiro Izutsu
##--------------------------------------------------
## sd_042.py
## Ver 0.00 2025.06.17 GUI 対応版
## Ver 0.01 2025.06.20 Loop count
import warnings
warnings.simplefilter('ignore')
# Color Escape Code
GREEN = '\033[1;32m'
RED = '\033[1;31m'
NOCOLOR = '\033[0m'
YELLOW = '\033[1;33m'
CYAN = '\033[1;36m'
BLUE = '\033[1;34m'
# インポート&初期設定
import os
import numpy as np
import random
import time
import csv
import cv2
import PySimpleGUI as sg
import sd_041 as sd
import my_logging
import my_csv
import my_thumbnail
# 定数定義
DEF_THEME = 'BlueMono'
CANVAS_SIZE = 512
KEY_CANCEL = '-Cancel-'
KEY_IMAGE = '-Image-'
KEY_OUTPATH = '-Output-'
KEY_MODEL = '-Model-'
KEY_MODELSEL = '-Model_sel-'
KEY_PROMPT_JP = '-PromptJP-'
KEY_PROMPT = '-Prompt-'
KEY_WIDTH = '-Width-'
KEY_HEIGHT = '-Height-'
KEY_SEED_INPUT = '-Seed_input-'
KEY_SEED = '-Seed-'
KEY_STEP = '-Step-'
KEY_SCALE = '-Scale-'
KEY_CPU = '-CPU-'
KEY_GPU = '-GPU-'
KEY_DEVICE = '-Device-'
KEY_EXIT = '-Exit-'
KEY_GENERATE = '-Generate-'
KEY_LOOP = '-Loop-'
SKEY_RESULT_PATH = '-result_path-'
SKEY_RESULT_FILE = '-result_file-'
SKEY_MODEL_DIR = '-model_dir-'
SKEY_DEF_OUTPATH = '-default_image-'
KEY_TIME = '-Time-'
DEF_IMAGE = './sd_results/sd_00000_12345678.png'
CSV_LOG_FILE = 'result.csv'
# タイトル
title = 'Stable Diffusion with diffusers(042) Ver 0.01'
# ウィジェットのデータの取得(1回目)
def get_paramlist(window, values, param):
param[KEY_MODEL] = window[KEY_MODEL].DisplayText
param[KEY_WIDTH] = int(values[KEY_WIDTH])
param[KEY_HEIGHT] = int(values[KEY_HEIGHT])
param[KEY_SEED] = sd._get_seed_value2(values[KEY_SEED_INPUT])
param[KEY_STEP] = int(values[KEY_STEP])
param[KEY_SCALE] = float(values[KEY_SCALE])
param[KEY_DEVICE] = 'cpu' if values[KEY_CPU] == True else 'cuda'
param[KEY_PROMPT_JP] = values[KEY_PROMPT_JP]
param[KEY_PROMPT] = sd._get_prompt2(param[KEY_PROMPT_JP])
param[KEY_LOOP] = int(values[KEY_LOOP])
if param[KEY_LOOP] < 1:
param[KEY_LOOP] = 1
if param[KEY_LOOP] > 50:
param[KEY_LOOP] = 50
# ウィジェットのデータの取得(2回目以降)
def get_paramlist2(window, values, param):
if int(values[KEY_SEED_INPUT]) > 0:
param[KEY_SEED] = param[KEY_SEED] + 1
else:
param[KEY_SEED] = sd._get_seed_value2(values[KEY_SEED_INPUT])
# 生成画像のファイル名からシード値を得る
def path2seed(filepath):
s = os.path.splitext(os.path.basename(filepath))[0]
n = s.rfind('_')
return int(s[n + 1:])
# 経過時間(秒)を hh:mm:ssフォーマットに変換
def elapsed_time_str(seconds):
seconds = int(seconds + 0.5) # 秒数を四捨五入
h = seconds // 3600 # 時の取得
m = (seconds - h * 3600) // 60 # 分の取得
s = seconds - h * 3600 - m * 60 # 秒の取得
return f"{h:02}:{m:02}:{s:02}" # hh:mm:ss形式の文字列で返す
# 結果を CSV ファイルに残す
def result_csv(csvfile, param, logger):
my_csv_treatment = my_csv.CSVtreatment(csvfile, 'utf_8_sig')
s = my_csv_treatment.read_csv()
if len(s) ==0:
data = []
data.append(KEY_OUTPATH)
data.append(KEY_SEED)
data.append(KEY_PROMPT_JP)
data.append(KEY_PROMPT)
data.append(KEY_WIDTH)
data.append(KEY_HEIGHT)
data.append(KEY_STEP)
data.append(KEY_SCALE)
data.append(SKEY_MODEL_DIR)
data.append(KEY_MODEL)
data.append(KEY_DEVICE)
data.append(KEY_TIME)
data.append(KEY_LOOP)
my_csv_treatment.write_csv(data)
logger.debug(data)
data = []
data.append(param[KEY_OUTPATH])
data.append(str(param[KEY_SEED]))
data.append(param[KEY_PROMPT_JP])
data.append(param[KEY_PROMPT])
data.append(str(param[KEY_WIDTH]))
data.append(str(param[KEY_HEIGHT]))
data.append(str(param[KEY_STEP]))
data.append(str(param[KEY_SCALE]))
data.append(param[SKEY_MODEL_DIR])
data.append(param[KEY_MODEL])
data.append(param[KEY_DEVICE])
data.append(param[KEY_TIME])
data.append(param[KEY_LOOP])
my_csv_treatment.append_csv(data)
logger.debug(data)
# 結果ログからパラメータを取得
def read_result_csv(csvfile, param, logger, imgfile = '0' ):
bf = False
try:
with open(csvfile, 'r', encoding = 'utf_8_sig') as f:
reader = csv.DictReader(f)
if imgfile == '0': # 最初の行
for dd in reader:
bf = True
break
elif imgfile == '1': # 最後の行
for dd in reader:
bf = True
continue
else: # 出力ファイルの行
for dd in reader:
if dd[KEY_OUTPATH] == imgfile:
bf = True
break
except FileNotFoundError as e:
logger.error(e)
except csv.Error as e:
logger.debug(e)
if bf:
param[KEY_OUTPATH] = dd[KEY_OUTPATH]
param[KEY_SEED_INPUT] = int(dd[KEY_SEED]) # シード値を固定
param[KEY_PROMPT_JP] = dd[KEY_PROMPT_JP]
param[KEY_PROMPT] = dd[KEY_PROMPT]
param[KEY_WIDTH] = int(dd[KEY_WIDTH])
param[KEY_HEIGHT] = int(dd[KEY_HEIGHT])
param[KEY_STEP] = int(dd[KEY_STEP])
param[KEY_SCALE] = float(dd[KEY_SCALE])
param[SKEY_MODEL_DIR] = dd[SKEY_MODEL_DIR]
param[KEY_MODEL] = dd[KEY_MODEL]
param[KEY_DEVICE] = dd[KEY_DEVICE]
logout_data(dd, logger)
return bf
# 動的パラメータの出力
def logout_data(dd, logger):
logger.debug('---------------------------------------------')
logger.debug(dd[KEY_OUTPATH])
logger.debug(int(dd[KEY_SEED]))
logger.debug(dd[KEY_PROMPT_JP])
logger.debug(dd[KEY_PROMPT])
logger.debug(int(dd[KEY_WIDTH]))
logger.debug(int(dd[KEY_HEIGHT]))
logger.debug(int(dd[KEY_STEP]))
logger.debug(float(dd[KEY_SCALE]))
logger.debug(dd[SKEY_MODEL_DIR])
logger.debug(dd[KEY_MODEL])
logger.debug(dd[KEY_DEVICE])
logger.debug('---------------------------------------------')
# 画像生成
def generate_image(param):
model = param[SKEY_MODEL_DIR] + '/' + param[KEY_MODEL]
prompt = param[KEY_PROMPT]
width = param[KEY_WIDTH]
height = param[KEY_HEIGHT]
seed = param[KEY_SEED]
num_inference_steps = param[KEY_STEP]
guidance_scale = param[KEY_SCALE]
device = param[KEY_DEVICE]
out_path = param[SKEY_RESULT_PATH] + '/' + sd.make_filename_by_seq(param[SKEY_RESULT_PATH], param[SKEY_RESULT_FILE], seq_digit = 5, ex = seed)
param[KEY_OUTPATH] = out_path
logger.debug(f'model: {model}')
logger.debug(f'prompt: {prompt}')
logger.debug(f'width: {width}')
logger.debug(f'height: {height}')
logger.debug(f'seed: {seed}')
logger.debug(f'num_inference_steps: {num_inference_steps}')
logger.debug(f'guidance_scale: {guidance_scale}')
logger.debug(f'device: {device}')
logger.debug(f'loop: {param[KEY_LOOP]}')
logger.debug(f'out_path: {out_path}')
image = sd.image_generation(model, prompt, seed, num_inference_steps, guidance_scale, width, height, device)
image.save(out_path)
logger.info(f'result_file: {out_path}')
sd.device_empty_cache(device) # メモリー開放
# ** main関数 **
def main(opt, logger):
# ------------------------------------------
# キャンバスをクリア
def clear_canvas(key, msg, color):
frame = np.zeros((CANVAS_SIZE, CANVAS_SIZE, 3), np.uint8)
frame[:,:,] = 0xf0
return msg_out_canvas(key, frame, msg, color)
def msg_out_canvas(key, frame, msg, color):
x0,y0,x1,y1 = cv2_putText(img=frame, text=msg, org=(CANVAS_SIZE//2, CANVAS_SIZE//2), fontFace=font_face, fontScale=16, color=color, mode=2,areaf=True)
cv2.rectangle(frame,(x0-8, y0), (x1+8, y1), (0xf0,0xf0,0xf0), -1)
img = cv2_putText(img=frame, text=msg, org=(CANVAS_SIZE//2, CANVAS_SIZE//2), fontFace=font_face, fontScale=16, color=color, mode=2)
img = cv2.imencode('.png', frame)[1].tobytes()
window[key].update(img)
return frame
# キャンバスへ画像の表示
def update_canvas(key, imgfile):
if os.path.isfile(imgfile):
frame = cv2.imread(imgfile)
frame = cv2.resize(frame, dsize = (CANVAS_SIZE, CANVAS_SIZE))
img = cv2.imencode('.png', frame)[1].tobytes()
window[key].update(img)
else:
frame = clear_canvas(key, 'Generate Image', (0,0,0))
return frame
# ウイジェットの更新
def update_widget():
frame = update_canvas(KEY_IMAGE, param[KEY_OUTPATH])
window[KEY_OUTPATH].update(param[KEY_OUTPATH])
window[KEY_SEED].update('')
window[KEY_SEED_INPUT].update(param[KEY_SEED_INPUT])
window[KEY_PROMPT_JP].update(param[KEY_PROMPT_JP])
window[KEY_PROMPT].update(param[KEY_PROMPT])
window[KEY_MODEL].update(param[KEY_MODEL])
window[KEY_WIDTH].update(param[KEY_WIDTH])
window[KEY_HEIGHT].update(param[KEY_HEIGHT])
window[KEY_STEP].update(param[KEY_STEP])
window[KEY_SCALE].update(param[KEY_SCALE])
window[KEY_CPU].update(param[KEY_DEVICE] == 'cpu')
window[KEY_GPU].update(param[KEY_DEVICE] == 'cuda')
return frame
# ------------------------------------------
# パラメータ設定
device = sd._get_device(opt, logger)
result_path = sd._get_result_path(opt, logger)
result_file = sd._get_result_file(opt, logger)
prompt = sd._get_prompt(opt, logger)
model_path = sd._get_model_path(opt, logger)
height, width = sd._get_image_size(opt, logger)
seed = path2seed(DEF_IMAGE) # 初期ファイル名に含まれるシード値
num_inference_steps = sd._get_inference_steps(opt, logger)
guidance_scale = sd._get_guidance_scale(opt, logger)
param = {}
param[KEY_OUTPATH] = DEF_IMAGE
param[KEY_PROMPT_JP] = opt.prompt
param[KEY_PROMPT] = prompt
param[KEY_MODEL] = opt.model_path
param[KEY_WIDTH] = width
param[KEY_HEIGHT] = height
param[KEY_SEED_INPUT] = opt.seed
param[KEY_SEED] = seed
param[KEY_STEP] = num_inference_steps
param[KEY_SCALE] = guidance_scale
param[KEY_DEVICE] = device
param[KEY_LOOP] = 1
param[KEY_TIME] = elapsed_time_str(0)
param[SKEY_RESULT_PATH] = result_path
param[SKEY_RESULT_FILE] = result_file
param[SKEY_MODEL_DIR] = opt.model_dir
logout_data(param, logger)
csvfile = param[SKEY_RESULT_PATH]+ '/' + CSV_LOG_FILE # 出力画像ログファイル名
# 出力フォルダ
os.makedirs(result_path, exist_ok = True)
# フォント取得
from my_puttext import get_font, cv2_putText
font_face = get_font()
# ウィンドウのテーマ
sg.theme(DEF_THEME)
canvas_img = sg.Image(size = (CANVAS_SIZE, CANVAS_SIZE), key=KEY_IMAGE)
# ウィンドウのレイアウト
layout = [[sg.Text('Stable Diffusion with diffusers', size=(30, 1), justification='center', font='Helvetica 20')],
[canvas_img],
[sg.Text("Output File", size=(14, 1)), sg.Text(param[KEY_OUTPATH], size=(48,1), key=KEY_OUTPATH)],
[sg.Text("Model", size=(14, 1)), sg.Text(param[KEY_MODEL], size=(38,1), text_color='#008800', background_color='LightSteelBlue1', key=KEY_MODEL),sg.Button('Model', size=(6, 1), key=KEY_MODELSEL)],
[sg.Text("Prompt input", size=(14, 1)), sg.Multiline(param[KEY_PROMPT_JP], size=(52,4), key=KEY_PROMPT_JP)],
[sg.Text("Prompt", size=(14, 1)), sg.Multiline(param[KEY_PROMPT], size=(52,4), text_color='#008800', background_color='LightSteelBlue1', key=KEY_PROMPT)],
[sg.Text("Image size (pixel)", size=(14, 1)), sg.Text("Width: ", size=(4, 1)), sg.Input(param[KEY_WIDTH], size=(10,1), key=KEY_WIDTH), sg.Text("Height: ", size=(4, 1)), sg.Input(param[KEY_HEIGHT], size=(10,1), key=KEY_HEIGHT)],
[sg.Text("Seed (-1=Random)", size=(14, 1)), sg.Input(param[KEY_SEED_INPUT], size=(20,1), key=KEY_SEED_INPUT), sg.Text(param[KEY_SEED], size=(20,1), text_color='#008800', background_color='LightSteelBlue1', key=KEY_SEED)],
[sg.Text("Detail (Steps)", size=(14, 1)), sg.Slider((10, 150), float(param[KEY_STEP]), 1, orientation='h', size=(42, 5), key=KEY_STEP)],
[sg.Text("Guidance Scale", size=(14, 1)), sg.Slider((1, 50), float(param[KEY_SCALE]), 0.1, orientation='h', size=(42, 5), key=KEY_SCALE)],
[sg.Text("Device", size=(14, 1)), sg.Radio('CPU', group_id='device', default=(device == 'cpu'), key=KEY_CPU), sg.Radio("GPU", group_id='device', default=(device == 'cuda'), key=KEY_GPU)],
[sg.Text("Loop count (1-50)", size=(14, 1)), sg.Input(param[KEY_LOOP], size=(2,1), justification='right', key=KEY_LOOP), sg.Text("", size=(4, 1)), sg.Button('Generate', size=(10, 1), key=KEY_GENERATE), sg.Text("", size=(10, 1)), sg.Button('Exit', size=(10, 1), key=KEY_EXIT)]
]
# ウィンドウオブジェクトの作成
window = sg.Window(title, layout, finalize=True, return_keyboard_events=True)
# ユーザーイベントの定義
canvas_img.bind('<ButtonPress>', '_click_on')
# キャンバス初期化
ss = param[KEY_SEED_INPUT] # 最初のシード入力値
bf = read_result_csv(csvfile, param, logger, '1')
if bf:
frame = update_widget()
param[KEY_SEED] = param[KEY_SEED_INPUT]
param[KEY_SEED_INPUT] = ss
window[KEY_SEED].update(param[KEY_SEED])
window[KEY_SEED_INPUT].update(param[KEY_SEED_INPUT] )
else:
frame = update_canvas(KEY_IMAGE, param[KEY_OUTPATH])
param[SKEY_DEF_OUTPATH] = param[KEY_OUTPATH] # 現在の画像パス
new_make_f = False
window[KEY_PROMPT].update(disabled = True)
cv2.imwrite('test.png', frame)
# イベントのループ
while True:
event, values = window.read(timeout = 30)
# 画像生成
if new_make_f:
# 処理プロセス
logger.info(f'{CYAN}** Start {param[KEY_LOOP]} **{NOCOLOR}')
start_time = time.time()
generate_image(param)
param[KEY_TIME] = elapsed_time_str(time.time() - start_time)
result_csv(csvfile, param, logger)
frame = update_canvas(KEY_IMAGE, param[KEY_OUTPATH])
param[SKEY_DEF_OUTPATH] = param[KEY_OUTPATH] # 現在の画像パス
window[KEY_OUTPATH].update(param[KEY_OUTPATH])
param[KEY_LOOP] = param[KEY_LOOP] - 1
window[KEY_LOOP].update(param[KEY_LOOP])
if param[KEY_LOOP] < 1:
param[KEY_LOOP] = 1
window[KEY_LOOP].update(param[KEY_LOOP], disabled = False)
window[KEY_GENERATE].update(disabled = False)
window[KEY_EXIT].update(disabled = False)
new_make_f = False
else:
get_paramlist2(window, values, param)
window[KEY_SEED].update(param[KEY_SEED])
logger.info(f'{CYAN}** Complete **{NOCOLOR} {param[KEY_TIME]}')
# 終了
if event == KEY_EXIT or event == sg.WIN_CLOSED:
break
# KEY_IMAGE
if event == KEY_IMAGE + '_click_on':
logger.debug(f'{event}')
window[KEY_GENERATE].update(disabled = True)
window[KEY_EXIT].update(disabled = True)
def_file = param[SKEY_DEF_OUTPATH]
if os.path.isfile(def_file):
imgfile = my_thumbnail.image_dialog(def_file, 'Image file select', my_thumbnail.DEF_THEME, 10, 4, ret = '', logger = logger)
if os.path.isfile(imgfile):
logger.debug(f'Output select: {imgfile}')
bf = read_result_csv(csvfile, param, logger, imgfile)
if bf:
update_widget()
param[SKEY_DEF_OUTPATH] = param[KEY_OUTPATH]
window[KEY_GENERATE].update(disabled = False)
window[KEY_EXIT].update(disabled = False)
# Genarate ボタン
if event == KEY_GENERATE:
logger.debug(f'{event}')
window[KEY_LOOP].update(disabled = True)
window[KEY_GENERATE].update(disabled = True)
window[KEY_EXIT].update(disabled = True)
get_paramlist(window, values, param)
frame = msg_out_canvas(KEY_IMAGE, frame, 'Generating ...', (240,0,0))
window[KEY_OUTPATH].update('')
window[KEY_PROMPT].update(param[KEY_PROMPT])
window[KEY_SEED].update(param[KEY_SEED])
new_make_f = True
# ウィンドウ終了処理
window.close()
# main関数エントリーポイント(実行開始)
if __name__ == "__main__":
import datetime
parser = sd.parse_args()
opt = parser.parse_args()
# アプリケーション・ログ設定
module = os.path.basename(__file__)
module_name = os.path.splitext(module)[0]
logger = my_logging.get_module_logger_sel(module_name, int(opt.log))
sd.display_info(opt, title)
start_time = datetime.datetime.now() # 時間計測開始
main(opt, logger)
# 経過時間
end_time = datetime.datetime.now()
print(start_time.strftime('\nprocessing start >>\t %Y/%m/%d %H:%M:%S'))
print(end_time.strftime('processing end >>\t %Y/%m/%d %H:%M:%S'))
print('processing time >>\t', end_time - start_time)
logger.info('\nFinished.\n')
※ 上記ソースコードは表示の都合上、半角コード '}' が 全角 '}'になっていることに注意