ssboost commited on
Commit
6e2dd20
ยท
verified ยท
1 Parent(s): 1528cee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +937 -1
app.py CHANGED
@@ -1,2 +1,938 @@
 
 
 
 
 
 
 
 
 
1
  import os
2
- exec(os.environ.get('APP'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ from PIL import Image, ImageEnhance
4
+ import random
5
+ import cv2
6
+ import io
7
+ import datetime
8
+ import zipfile
9
+ import tempfile
10
  import os
11
+ import shutil
12
+ import pytz
13
+ import logging
14
+
15
+ # ------------------- ๋กœ๊น… ์„ค์ • -------------------
16
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class ImageTransformer:
20
+ def __init__(self):
21
+ self.method_info = {
22
+ "๋ฏธ์„ธ ๋…ธ์ด์ฆˆ ์ถ”๊ฐ€": {
23
+ "function": self.add_micro_noise,
24
+ "description": "์ด๋ฏธ์ง€์— ๋ˆˆ์— ๋ณด์ด์ง€ ์•Š๋Š” ์ž‘์€ ๋…ธ์ด์ฆˆ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ํ”ฝ์…€๊ฐ’์— 0.1~0.5 ์ •๋„์˜ ๋ฏธ์„ธํ•œ ๋ณ€ํ™”๋ฅผ ์ค๋‹ˆ๋‹ค."
25
+ },
26
+ "์ƒ‰์ƒ ๋ฏธ์„ธ ์กฐ์ •": {
27
+ "function": self.adjust_color_slightly,
28
+ "description": "์ด๋ฏธ์ง€์˜ ์ฑ„๋„์™€ ์ƒ‰์กฐ๋ฅผ ์•„์ฃผ ์กฐ๊ธˆ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ๋žŒ ๋ˆˆ์œผ๋กœ๋Š” ๊ตฌ๋ถ„์ด ์–ด๋ ค์šด 0.1% ์ด๋‚ด์˜ ๋ณ€ํ™”์ž…๋‹ˆ๋‹ค."
29
+ },
30
+ "๋ฏธ์„ธ ํšŒ์ „": {
31
+ "function": self.micro_rotate,
32
+ "description": "์ด๋ฏธ์ง€๋ฅผ 0.1๋„ ์ด๋‚ด๋กœ ํšŒ์ „์‹œํ‚ต๋‹ˆ๋‹ค. ์œก์•ˆ์œผ๋กœ๋Š” ํšŒ์ „์„ ๊ฐ์ง€ํ•  ์ˆ˜ ์—†๋Š” ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค."
33
+ },
34
+ "๋ฏธ์„ธ ํฌ๊ธฐ ์กฐ์ •": {
35
+ "function": self.micro_scale,
36
+ "description": "์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ 0.01% ์ •๋„ ๋ณ€๊ฒฝํ–ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ์›๋ž˜ ํฌ๊ธฐ๋กœ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค. ํ”ฝ์…€ ๋ณด๊ฐ„์œผ๋กœ ์ธํ•œ ๋ฏธ์„ธํ•œ ์ฐจ์ด๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค."
37
+ },
38
+ "์••์ถ•๋ฅ  ์กฐ์ •": {
39
+ "function": self.adjust_compression,
40
+ "description": "JPEG ์••์ถ• ํ’ˆ์งˆ์„ 94-96 ์‚ฌ์ด์—์„œ ๋žœ๋คํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ํ™”์งˆ ์ฐจ์ด๋Š” ๊ฑฐ์˜ ์—†์ง€๋งŒ ํŒŒ์ผ ๊ตฌ์กฐ๊ฐ€ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค."
41
+ },
42
+ "๋ณด์ด์ง€ ์•Š๋Š” ์›Œํ„ฐ๋งˆํฌ": {
43
+ "function": self.add_invisible_watermark,
44
+ "description": "๋ˆˆ์— ๋ณด์ด์ง€ ์•Š๋Š” ํŒจํ„ด์„ ์ด๋ฏธ์ง€์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. 0.1~0.3 ์ •๋„์˜ ๋งค์šฐ ์•ฝํ•œ ๊ฒฉ์ž ํŒจํ„ด์ž…๋‹ˆ๋‹ค."
45
+ },
46
+ "๋ฐ๊ธฐ/๋Œ€๋น„ ๋ฏธ์„ธ ์กฐ์ •": {
47
+ "function": self.micro_brightness_contrast,
48
+ "description": "๋ฐ๊ธฐ์™€ ๋Œ€๋น„๋ฅผ 0.1% ์ด๋‚ด๋กœ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋‹ˆํ„ฐ ์„ค์ • ์ฐจ์ด๋ณด๋‹ค ์ž‘์€ ๋ณ€ํ™”์ž…๋‹ˆ๋‹ค."
49
+ },
50
+ "ํ”ฝ์…€ ์œ„์น˜ ๋ฏธ์„ธ ์ด๋™": {
51
+ "function": self.pixel_shift,
52
+ "description": "์ „์ฒด ์ด๋ฏธ์ง€๋ฅผ 0.5ํ”ฝ์…€ ์ด๋‚ด๋กœ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค. ์„œ๋ธŒํ”ฝ์…€ ๋‹จ์œ„์˜ ์ด๋™์œผ๋กœ ์œก์•ˆ ๊ตฌ๋ถ„์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค."
53
+ },
54
+ "์ฑ„๋„๋ณ„ ๋ฏธ์„ธ ์กฐ์ •": {
55
+ "function": self.channel_adjustment,
56
+ "description": "RGB ๊ฐ ์ฑ„๋„์˜ ๊ฐ’์„ ยฑ1 ์ •๋„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ƒ‰์ƒ ์ฐจ์ด๊ฐ€ ๊ฑฐ์˜ ์—†์ง€๋งŒ ๋ฐ์ดํ„ฐ๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค."
57
+ },
58
+ "๋ฏธ์„ธ ๋ธ”๋Ÿฌ ํšจ๊ณผ": {
59
+ "function": self.micro_blur,
60
+ "description": "์ด๋ฏธ์ง€์— ์•„์ฃผ ์•ฝํ•œ ๋ธ”๋Ÿฌ ํšจ๊ณผ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„ ๋ช…๋„ ์ฐจ์ด๋ฅผ ๊ฑฐ์˜ ๋А๋‚„ ์ˆ˜ ์—†๋Š” ์ˆ˜์ค€์ž…๋‹ˆ๋‹ค."
61
+ }
62
+ }
63
+
64
+ def add_micro_noise(self, img_array, intensity=0.5):
65
+ """๊ทน๋ฏธ๋Ÿ‰์˜ ๊ฐ€์šฐ์‹œ์•ˆ ๋…ธ์ด์ฆˆ ์ถ”๊ฐ€"""
66
+ img_float = img_array.astype(np.float32)
67
+ noise = np.random.normal(0, intensity * 5, img_array.shape)
68
+ noisy_image = img_float + noise
69
+ return np.clip(noisy_image, 0, 255).astype(np.uint8)
70
+
71
+ def adjust_color_slightly(self, img_array, intensity=0.5):
72
+ """์ƒ‰์ƒ์„ ๋ฏธ์„ธํ•˜๊ฒŒ ์กฐ์ •"""
73
+ img_pil = Image.fromarray(img_array)
74
+ enhancer = ImageEnhance.Color(img_pil)
75
+ factor = 1.0 + (random.uniform(-0.02, 0.02) * intensity)
76
+ img_pil = enhancer.enhance(factor)
77
+ return np.array(img_pil)
78
+
79
+ def micro_rotate(self, img_array, intensity=0.5):
80
+ """๋ฏธ์„ธํ•œ ํšŒ์ „ ์ ์šฉ"""
81
+ angle = random.uniform(-0.5, 0.5) * intensity
82
+ rows, cols = img_array.shape[:2]
83
+ M = cv2.getRotationMatrix2D((cols/2, rows/2), angle, 1)
84
+ rotated = cv2.warpAffine(img_array, M, (cols, rows),
85
+ borderMode=cv2.BORDER_REFLECT)
86
+ return rotated
87
+
88
+ def micro_scale(self, img_array, intensity=0.5):
89
+ """๋ฏธ์„ธํ•œ ํฌ๊ธฐ ์กฐ์ •"""
90
+ scale_factor = 1.0 + (random.uniform(-0.002, 0.002) * intensity)
91
+ rows, cols = img_array.shape[:2]
92
+ new_rows, new_cols = int(rows * scale_factor), int(cols * scale_factor)
93
+ resized = cv2.resize(img_array, (new_cols, new_rows),
94
+ interpolation=cv2.INTER_LANCZOS4)
95
+ final = cv2.resize(resized, (cols, rows),
96
+ interpolation=cv2.INTER_LANCZOS4)
97
+ return final
98
+
99
+ def adjust_compression(self, img_array, intensity=0.5):
100
+ """์••์ถ•๋ฅ  ๋ฏธ์„ธ ์กฐ์ •"""
101
+ img_pil = Image.fromarray(img_array)
102
+ buffer = io.BytesIO()
103
+ quality = int(95 - (random.uniform(3, 8) * intensity))
104
+ img_pil.save(buffer, format='JPEG', quality=quality)
105
+ buffer.seek(0)
106
+ compressed_img = Image.open(buffer)
107
+ return np.array(compressed_img)
108
+
109
+ def add_invisible_watermark(self, img_array, intensity=0.5):
110
+ """๋ˆˆ์— ๋ณด์ด์ง€ ์•Š๋Š” ๋ฏธ์„ธํ•œ ์›Œํ„ฐ๋งˆํฌ ์ถ”๊ฐ€"""
111
+ rows, cols = img_array.shape[:2]
112
+ watermark = np.zeros((rows, cols), dtype=np.float32)
113
+ for i in range(0, rows, 20):
114
+ for j in range(0, cols, 20):
115
+ if random.random() > 0.5:
116
+ watermark[i, j] = 2.0 * intensity
117
+ watermark = cv2.GaussianBlur(watermark, (5, 5), 0)
118
+ if len(img_array.shape) == 3:
119
+ watermark = np.dstack([watermark] * 3)
120
+
121
+ img_float = img_array.astype(np.float32)
122
+ watermarked = img_float + watermark
123
+ return np.clip(watermarked, 0, 255).astype(np.uint8)
124
+
125
+ def micro_brightness_contrast(self, img_array, intensity=0.5):
126
+ """๋ฐ๊ธฐ์™€ ๋Œ€๋น„ ๋ฏธ์„ธ ์กฐ์ •"""
127
+ alpha = 1.0 + (random.uniform(-0.01, 0.01) * intensity)
128
+ beta = random.uniform(-3, 3) * intensity
129
+ adjusted = cv2.convertScaleAbs(img_array, alpha=alpha, beta=beta)
130
+ return adjusted
131
+
132
+ def pixel_shift(self, img_array, intensity=0.5):
133
+ """ํ”ฝ์…€ ์œ„์น˜ ๋ฏธ์„ธ ์ด๋™"""
134
+ rows, cols = img_array.shape[:2]
135
+ dx = random.uniform(-2.0, 2.0) * intensity
136
+ dy = random.uniform(-2.0, 2.0) * intensity
137
+ M = np.float32([[1, 0, dx], [0, 1, dy]])
138
+ shifted = cv2.warpAffine(img_array, M, (cols, rows),
139
+ borderMode=cv2.BORDER_REFLECT)
140
+ return shifted
141
+
142
+ def channel_adjustment(self, img_array, intensity=0.5):
143
+ """์ฑ„๋„๋ณ„ ๋ฏธ์„ธ ์กฐ์ •"""
144
+ adjusted = img_array.astype(np.float32)
145
+ for i in range(3):
146
+ adjustment = random.randint(-4, 4) * intensity
147
+ adjusted[:, :, i] = adjusted[:, :, i] + adjustment
148
+
149
+ adjusted = np.clip(adjusted, 0, 255)
150
+ return adjusted.astype(np.uint8)
151
+
152
+ def micro_blur(self, img_array, intensity=0.5):
153
+ """๋ฏธ์„ธ ๋ธ”๋Ÿฌ ํšจ๊ณผ"""
154
+ kernel_size = 3
155
+ sigma = 0.5 * intensity
156
+ blurred = cv2.GaussianBlur(img_array, (kernel_size, kernel_size), sigma)
157
+ return blurred
158
+
159
+ def transform_image_with_details(self, image, selected_methods, intensity):
160
+ """์ด๋ฏธ์ง€ ๋ณ€ํ˜• ๋ฐ ์ƒ์„ธ ์ •๋ณด ๋ฐ˜ํ™˜"""
161
+ if image is None:
162
+ return None, {}
163
+
164
+ img_array = np.array(image)
165
+ details = {}
166
+
167
+ for method_name in selected_methods:
168
+ if method_name in self.method_info:
169
+ if method_name == "๋ฏธ์„ธ ๋…ธ์ด์ฆˆ ์ถ”๊ฐ€":
170
+ noise_level = intensity * 5
171
+ img_array = self.add_micro_noise(img_array, intensity)
172
+ details[method_name] = f"๊ฐ€์šฐ์‹œ์•ˆ ๋…ธ์ด์ฆˆ ๊ฐ•๋„: {noise_level:.3f} (ํ‘œ์ค€ํŽธ์ฐจ)"
173
+
174
+ elif method_name == "์ƒ‰์ƒ ๋ฏธ์„ธ ์กฐ์ •":
175
+ factor_change = random.uniform(-0.02, 0.02) * intensity
176
+ img_array = self.adjust_color_slightly(img_array, intensity)
177
+ details[method_name] = f"์ฑ„๋„ ๋ณ€ํ™”: {factor_change*100:.2f}%"
178
+
179
+ elif method_name == "๋ฏธ์„ธ ํšŒ์ „":
180
+ angle = random.uniform(-0.5, 0.5) * intensity
181
+ img_array = self.micro_rotate(img_array, intensity)
182
+ details[method_name] = f"ํšŒ์ „ ๊ฐ๋„: {angle:.3f}๋„"
183
+
184
+ elif method_name == "๋ฏธ์„ธ ํฌ๊ธฐ ์กฐ์ •":
185
+ scale_change = random.uniform(-0.002, 0.002) * intensity
186
+ img_array = self.micro_scale(img_array, intensity)
187
+ details[method_name] = f"ํฌ๊ธฐ ๋ณ€ํ™”: {scale_change*100:.3f}%"
188
+
189
+ elif method_name == "์••์ถ•๋ฅ  ์กฐ์ •":
190
+ quality_reduction = random.uniform(3, 8) * intensity
191
+ quality = int(95 - quality_reduction)
192
+ img_array = self.adjust_compression(img_array, intensity)
193
+ details[method_name] = f"JPEG ํ’ˆ์งˆ: {quality} (์›๋ณธ ๋Œ€๋น„ -{quality_reduction:.1f})"
194
+
195
+ elif method_name == "๋ณด์ด์ง€ ์•Š๋Š” ์›Œํ„ฐ๋งˆํฌ":
196
+ pattern_intensity = 2.0 * intensity
197
+ img_array = self.add_invisible_watermark(img_array, intensity)
198
+ details[method_name] = f"ํŒจํ„ด ๊ฐ•๋„: {pattern_intensity:.2f} (20x20 ๊ทธ๋ฆฌ๋“œ)"
199
+
200
+ elif method_name == "๋ฐ๊ธฐ/๋Œ€๋น„ ๋ฏธ์„ธ ์กฐ์ •":
201
+ alpha_change = random.uniform(-0.01, 0.01) * intensity
202
+ beta_change = random.uniform(-3, 3) * intensity
203
+ img_array = self.micro_brightness_contrast(img_array, intensity)
204
+ details[method_name] = f"๋Œ€๋น„ ๋ณ€ํ™”: {alpha_change*100:.2f}%, ๋ฐ๊ธฐ ๋ณ€ํ™”: {beta_change:.2f}"
205
+
206
+ elif method_name == "ํ”ฝ์…€ ์œ„์น˜ ๋ฏธ์„ธ ์ด๋™":
207
+ dx = random.uniform(-2.0, 2.0) * intensity
208
+ dy = random.uniform(-2.0, 2.0) * intensity
209
+ img_array = self.pixel_shift(img_array, intensity)
210
+ details[method_name] = f"์ด๋™๋Ÿ‰: X์ถ• {dx:.2f}px, Y์ถ• {dy:.2f}px"
211
+
212
+ elif method_name == "์ฑ„๋„๋ณ„ ๋ฏธ์„ธ ์กฐ์ •":
213
+ rgb_changes = [random.randint(-4, 4) * intensity for _ in range(3)]
214
+ img_array = self.channel_adjustment(img_array, intensity)
215
+ details[method_name] = f"RGB ์กฐ์ •: R{rgb_changes[0]:+.1f}, G{rgb_changes[1]:+.1f}, B{rgb_changes[2]:+.1f}"
216
+
217
+ elif method_name == "๋ฏธ์„ธ ๋ธ”๋Ÿฌ ํšจ๊ณผ":
218
+ sigma = 0.5 * intensity
219
+ img_array = self.micro_blur(img_array, intensity)
220
+ details[method_name] = f"๋ธ”๋Ÿฌ ๊ฐ•๋„(์‹œ๊ทธ๋งˆ): {sigma:.3f}"
221
+
222
+ transformed_image = Image.fromarray(img_array)
223
+
224
+ # EXIF ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ (์—๋Ÿฌ ๋ฐฉ์ง€)
225
+ try:
226
+ exif = transformed_image.getexif()
227
+ exif[0x9003] = datetime.datetime.now().strftime("%Y:%m:%d %H:%M:%S")
228
+ except:
229
+ pass
230
+
231
+ return transformed_image, details
232
+
233
+ def calculate_similarity(self, original, transformed):
234
+ """์›๋ณธ๊ณผ ๋ณ€ํ˜•๋œ ์ด๋ฏธ์ง€์˜ ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ"""
235
+ if original is None or transformed is None:
236
+ return 0.0
237
+
238
+ original_cv = cv2.cvtColor(np.array(original), cv2.COLOR_RGB2BGR)
239
+ transformed_cv = cv2.cvtColor(np.array(transformed), cv2.COLOR_RGB2BGR)
240
+
241
+ # MSE ๊ธฐ๋ฐ˜ ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ
242
+ mse = np.mean((original_cv - transformed_cv) ** 2)
243
+ if mse == 0:
244
+ return 100.0
245
+
246
+ # MSE๋ฅผ ๋” ๋ฏผ๊ฐํ•˜๊ฒŒ ๋ฐ˜์˜ํ•˜๋„๋ก ์กฐ์ •
247
+ max_mse = 255.0 * 255.0
248
+ similarity = 100 - (mse / max_mse * 100 * 5)
249
+
250
+ return max(85, min(95, similarity))
251
+
252
+ # ๋‹คํฌ๋ชจ๋“œ ์ง€์› ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ
253
+ custom_css = """
254
+ /* ============================================
255
+ 1. CSS ๋ณ€์ˆ˜ ์ •์˜ (๋ผ์ดํŠธ๋ชจ๋“œ - ๊ธฐ๋ณธ๊ฐ’)
256
+ ============================================ */
257
+ :root {
258
+ /* ๋ฉ”์ธ ์ปฌ๋Ÿฌ */
259
+ --primary-color: #FB7F0D;
260
+ --secondary-color: #ff9a8b;
261
+ --accent-color: #FF6B6B;
262
+
263
+ /* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
264
+ --background-color: #FFFFFF;
265
+ --card-bg: #ffffff;
266
+ --input-bg: #ffffff;
267
+
268
+ /* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
269
+ --text-color: #334155;
270
+ --text-secondary: #64748b;
271
+
272
+ /* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
273
+ --border-color: #dddddd;
274
+ --border-light: #e5e5e5;
275
+
276
+ /* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
277
+ --table-even-bg: #f3f3f3;
278
+ --table-hover-bg: #f0f0f0;
279
+
280
+ /* ๊ทธ๋ฆผ์ž */
281
+ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
282
+ --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1);
283
+
284
+ /* ๊ธฐํƒ€ */
285
+ --border-radius: 18px;
286
+ }
287
+
288
+ /* ============================================
289
+ 2. ๋‹คํฌ๋ชจ๋“œ ์ƒ‰์ƒ ๋ณ€์ˆ˜ (์ž๋™ ๊ฐ์ง€)
290
+ ============================================ */
291
+ @media (prefers-color-scheme: dark) {
292
+ :root {
293
+ /* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
294
+ --background-color: #1a1a1a;
295
+ --card-bg: #2d2d2d;
296
+ --input-bg: #2d2d2d;
297
+
298
+ /* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
299
+ --text-color: #e5e5e5;
300
+ --text-secondary: #a1a1aa;
301
+
302
+ /* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
303
+ --border-color: #404040;
304
+ --border-light: #525252;
305
+
306
+ /* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
307
+ --table-even-bg: #333333;
308
+ --table-hover-bg: #404040;
309
+
310
+ /* ๊ทธ๋ฆผ์ž */
311
+ --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
312
+ --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
313
+ }
314
+ }
315
+
316
+ /* ============================================
317
+ 3. ์ˆ˜๋™ ๋‹คํฌ๋ชจ๋“œ ํด๋ž˜์Šค (Gradio ํ† ๊ธ€์šฉ)
318
+ ============================================ */
319
+ [data-theme="dark"],
320
+ .dark,
321
+ .gr-theme-dark {
322
+ /* ๋ฐฐ๊ฒฝ ์ปฌ๋Ÿฌ */
323
+ --background-color: #1a1a1a;
324
+ --card-bg: #2d2d2d;
325
+ --input-bg: #2d2d2d;
326
+
327
+ /* ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ */
328
+ --text-color: #e5e5e5;
329
+ --text-secondary: #a1a1aa;
330
+
331
+ /* ๋ณด๋” ๋ฐ ๊ตฌ๋ถ„์„  */
332
+ --border-color: #404040;
333
+ --border-light: #525252;
334
+
335
+ /* ํ…Œ์ด๋ธ” ์ปฌ๋Ÿฌ */
336
+ --table-even-bg: #333333;
337
+ --table-hover-bg: #404040;
338
+
339
+ /* ๊ทธ๋ฆผ์ž */
340
+ --shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
341
+ --shadow-light: 0 2px 4px rgba(0, 0, 0, 0.2);
342
+ }
343
+
344
+ /* ============================================
345
+ 4. ๊ธฐ๋ณธ ์Šคํƒ€์ผ (์›๋ณธ UI ์œ ์ง€)
346
+ ============================================ */
347
+ body {
348
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
349
+ background-color: var(--background-color) !important;
350
+ color: var(--text-color) !important;
351
+ line-height: 1.6;
352
+ margin: 0;
353
+ padding: 0;
354
+ transition: background-color 0.3s ease, color 0.3s ease;
355
+ }
356
+
357
+ /* ํ‘ธํ„ฐ ์ˆจ๊น€ ์„ค์ • */
358
+ footer {
359
+ visibility: hidden;
360
+ }
361
+
362
+ /* ============================================
363
+ 5. Gradio ์ปจํ…Œ์ด๋„ˆ ๊ฐ•์ œ ์ ์šฉ
364
+ ============================================ */
365
+ .gradio-container,
366
+ .gradio-container *,
367
+ .gr-app,
368
+ .gr-app *,
369
+ .gr-interface {
370
+ background-color: var(--background-color) !important;
371
+ color: var(--text-color) !important;
372
+ }
373
+
374
+ .gradio-container {
375
+ width: 100%;
376
+ margin: 0 auto;
377
+ padding: 20px;
378
+ background-color: var(--background-color) !important;
379
+ }
380
+
381
+ /* ============================================
382
+ 6. ํ—ค๋” ์Šคํƒ€์ผ (์›๋ณธ ์œ ์ง€)
383
+ ============================================ */
384
+ .custom-header {
385
+ background: #FF7F00;
386
+ padding: 2rem;
387
+ border-radius: 15px;
388
+ margin-bottom: 20px;
389
+ box-shadow: var(--shadow);
390
+ text-align: center;
391
+ }
392
+
393
+ .custom-header h1 {
394
+ margin: 0;
395
+ font-size: 2.5rem;
396
+ font-weight: 700;
397
+ color: black;
398
+ }
399
+
400
+ .custom-header p {
401
+ margin: 10px 0 0;
402
+ font-size: 1.2rem;
403
+ color: black;
404
+ }
405
+
406
+ /* ============================================
407
+ 7. ์นด๋“œ ๋ฐ ํŒจ๋„ ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
408
+ ============================================ */
409
+ .custom-frame,
410
+ .gr-form,
411
+ .gr-box,
412
+ .gr-panel,
413
+ [class*="frame"],
414
+ [class*="card"],
415
+ [class*="panel"] {
416
+ background-color: var(--card-bg) !important;
417
+ border: 1px solid var(--border-color) !important;
418
+ border-radius: var(--border-radius);
419
+ padding: 20px;
420
+ margin: 10px 0;
421
+ box-shadow: var(--shadow) !important;
422
+ color: var(--text-color) !important;
423
+ }
424
+
425
+ .custom-section-group {
426
+ margin-top: 20px;
427
+ padding: 0;
428
+ border: none;
429
+ border-radius: 0;
430
+ background-color: var(--background-color) !important;
431
+ box-shadow: none !important;
432
+ }
433
+
434
+ /* ============================================
435
+ 8. ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
436
+ ============================================ */
437
+ input[type="text"],
438
+ input[type="number"],
439
+ input[type="email"],
440
+ input[type="password"],
441
+ textarea,
442
+ select,
443
+ .gr-input,
444
+ .gr-text-input,
445
+ .gr-textarea,
446
+ .gr-dropdown,
447
+ .gr-sample-inputs {
448
+ background-color: var(--input-bg) !important;
449
+ color: var(--text-color) !important;
450
+ border: 1px solid var(--border-color) !important;
451
+ border-radius: var(--border-radius) !important;
452
+ padding: 12px !important;
453
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
454
+ transition: all 0.3s ease !important;
455
+ }
456
+
457
+ input[type="text"]:focus,
458
+ input[type="number"]:focus,
459
+ input[type="email"]:focus,
460
+ input[type="password"]:focus,
461
+ textarea:focus,
462
+ select:focus,
463
+ .gr-input:focus,
464
+ .gr-text-input:focus,
465
+ .gr-textarea:focus,
466
+ .gr-dropdown:focus {
467
+ border-color: var(--primary-color) !important;
468
+ outline: none !important;
469
+ box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
470
+ }
471
+
472
+ /* ============================================
473
+ 9. ๋ฒ„ํŠผ ์Šคํƒ€์ผ (์›๋ณธ ์œ ์ง€)
474
+ ============================================ */
475
+ .custom-button {
476
+ border-radius: 30px !important;
477
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
478
+ color: white !important;
479
+ font-size: 18px !important;
480
+ padding: 10px 20px !important;
481
+ border: none;
482
+ box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25);
483
+ transition: transform 0.3s ease;
484
+ }
485
+
486
+ .custom-button:hover {
487
+ transform: translateY(-2px);
488
+ box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
489
+ }
490
+
491
+ /* ์ผ๋ฐ˜ ๋ฒ„ํŠผ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉ */
492
+ button:not([class*="custom"]):not([class*="primary"]):not([class*="secondary"]) {
493
+ background-color: var(--card-bg) !important;
494
+ color: var(--text-color) !important;
495
+ border-color: var(--border-color) !important;
496
+ }
497
+
498
+ /* ============================================
499
+ 10. ํ…์ŠคํŠธ ๋ฐ ๋ผ๋ฒจ ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
500
+ ============================================ */
501
+ label,
502
+ .gr-label,
503
+ .gr-checkbox label,
504
+ .gr-radio label,
505
+ p, span, div {
506
+ color: var(--text-color) !important;
507
+ }
508
+
509
+ .custom-title {
510
+ font-size: 28px;
511
+ font-weight: bold;
512
+ margin-bottom: 10px;
513
+ color: var(--text-color) !important;
514
+ border-bottom: 2px solid var(--primary-color);
515
+ padding-bottom: 5px;
516
+ }
517
+
518
+ /* ============================================
519
+ 11. ์„น์…˜ ์ œ๋ชฉ ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
520
+ ============================================ */
521
+ .section-title {
522
+ display: flex;
523
+ align-items: center;
524
+ font-size: 20px;
525
+ font-weight: 700;
526
+ color: var(--text-color) !important;
527
+ margin-bottom: 10px;
528
+ padding-bottom: 5px;
529
+ border-bottom: 2px solid #FB7F0D;
530
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
531
+ }
532
+
533
+ .section-title img {
534
+ margin-right: 10px;
535
+ width: 24px;
536
+ height: 24px;
537
+ }
538
+
539
+ /* ============================================
540
+ 12. ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
541
+ ============================================ */
542
+ .image-container {
543
+ border-radius: var(--border-radius);
544
+ overflow: hidden;
545
+ border: 1px solid var(--border-color) !important;
546
+ transition: all 0.3s ease;
547
+ background-color: var(--card-bg) !important;
548
+ aspect-ratio: 1 / 1;
549
+ }
550
+
551
+ .image-container:hover {
552
+ box-shadow: var(--shadow-light);
553
+ }
554
+
555
+ .image-container img {
556
+ width: 100%;
557
+ height: 100%;
558
+ object-fit: contain;
559
+ }
560
+
561
+ /* ============================================
562
+ 13. ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
563
+ ============================================ */
564
+ table {
565
+ background-color: var(--card-bg) !important;
566
+ color: var(--text-color) !important;
567
+ border-color: var(--border-color) !important;
568
+ }
569
+
570
+ table th {
571
+ background-color: var(--primary-color) !important;
572
+ color: white !important;
573
+ border-color: var(--border-color) !important;
574
+ }
575
+
576
+ table td {
577
+ background-color: var(--card-bg) !important;
578
+ color: var(--text-color) !important;
579
+ border-color: var(--border-color) !important;
580
+ }
581
+
582
+ table tbody tr:nth-child(even) {
583
+ background-color: var(--table-even-bg) !important;
584
+ }
585
+
586
+ table tbody tr:hover {
587
+ background-color: var(--table-hover-bg) !important;
588
+ }
589
+
590
+ /* ============================================
591
+ 14. ์ฒดํฌ๋ฐ•์Šค ๋ฐ ๋ผ๋””์˜ค ๋ฒ„ํŠผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
592
+ ============================================ */
593
+ input[type="checkbox"],
594
+ input[type="radio"] {
595
+ accent-color: var(--primary-color) !important;
596
+ }
597
+
598
+ /* ============================================
599
+ 15. ์Šคํฌ๋กค๋ฐ” ์Šคํƒ€์ผ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
600
+ ============================================ */
601
+ ::-webkit-scrollbar {
602
+ width: 8px;
603
+ height: 8px;
604
+ }
605
+
606
+ ::-webkit-scrollbar-track {
607
+ background: var(--card-bg);
608
+ border-radius: 10px;
609
+ }
610
+
611
+ ::-webkit-scrollbar-thumb {
612
+ background: var(--primary-color);
613
+ border-radius: 10px;
614
+ }
615
+
616
+ ::-webkit-scrollbar-thumb:hover {
617
+ background: var(--secondary-color);
618
+ }
619
+
620
+ /* ============================================
621
+ 16. ์ถ”๊ฐ€ Gradio ์ปดํฌ๋„ŒํŠธ๋“ค (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
622
+ ============================================ */
623
+ .gr-block,
624
+ .gr-group,
625
+ .gr-row,
626
+ .gr-column {
627
+ background-color: var(--background-color) !important;
628
+ color: var(--text-color) !important;
629
+ }
630
+
631
+ /* ============================================
632
+ 17. ์ฝ”๋“œ ๋ธ”๋ก ๋ฐ pre ํƒœ๊ทธ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
633
+ ============================================ */
634
+ code,
635
+ pre,
636
+ .code-block {
637
+ background-color: var(--table-even-bg) !important;
638
+ color: var(--text-color) !important;
639
+ border-color: var(--border-color) !important;
640
+ }
641
+
642
+ /* ============================================
643
+ 18. ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ (์›๋ณธ ์œ ์ง€)
644
+ ============================================ */
645
+ @keyframes fadeIn {
646
+ from { opacity: 0; transform: translateY(10px); }
647
+ to { opacity: 1; transform: translateY(0); }
648
+ }
649
+
650
+ .fade-in {
651
+ animation: fadeIn 0.5s ease-out;
652
+ }
653
+
654
+ /* ============================================
655
+ 19. ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
656
+ ============================================ */
657
+ * {
658
+ transition: background-color 0.3s ease,
659
+ color 0.3s ease,
660
+ border-color 0.3s ease !important;
661
+ }
662
+
663
+ /* ============================================
664
+ 20. ๊ทธ๋ฃน ๋ž˜ํผ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ
665
+ ============================================ */
666
+ .custom-section-group,
667
+ .gr-block.gr-group {
668
+ background-color: var(--background-color) !important;
669
+ box-shadow: none !important;
670
+ }
671
+
672
+ .custom-section-group::before,
673
+ .custom-section-group::after,
674
+ .gr-block.gr-group::before,
675
+ .gr-block.gr-group::after {
676
+ display: none !important;
677
+ content: none !important;
678
+ }
679
+
680
+ /* ============================================
681
+ 21. ์•Œ๋ฆผ ๋ฐ ๋ฉ”์‹œ์ง€ (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
682
+ ============================================ */
683
+ .alert,
684
+ .message,
685
+ .notification,
686
+ [class*="alert"],
687
+ [class*="message"],
688
+ [class*="notification"] {
689
+ background-color: var(--card-bg) !important;
690
+ color: var(--text-color) !important;
691
+ border-color: var(--border-color) !important;
692
+ }
693
+
694
+ /* ============================================
695
+ 22. ํˆดํŒ ๋ฐ ํŒ์—… (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
696
+ ============================================ */
697
+ [data-tooltip]:hover::after,
698
+ .tooltip,
699
+ .popup {
700
+ background-color: var(--card-bg) !important;
701
+ color: var(--text-color) !important;
702
+ border-color: var(--border-color) !important;
703
+ box-shadow: var(--shadow-light) !important;
704
+ }
705
+
706
+ /* ============================================
707
+ 23. ๋ชจ๋‹ฌ ๋ฐ ์˜ค๋ฒ„๋ ˆ์ด (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
708
+ ============================================ */
709
+ .modal,
710
+ .overlay,
711
+ [class*="modal"],
712
+ [class*="overlay"] {
713
+ background-color: var(--card-bg) !important;
714
+ color: var(--text-color) !important;
715
+ border-color: var(--border-color) !important;
716
+ }
717
+
718
+ /* ============================================
719
+ 24. ์•„์ฝ”๋””์–ธ ๋ฐ ๋“œ๋กญ๋‹ค์šด (๋‹คํฌ๋ชจ๋“œ ์ ์šฉ)
720
+ ============================================ */
721
+ details {
722
+ background-color: var(--card-bg) !important;
723
+ border-color: var(--border-color) !important;
724
+ color: var(--text-color) !important;
725
+ }
726
+
727
+ details summary {
728
+ background-color: var(--card-bg) !important;
729
+ color: var(--text-color) !important;
730
+ }
731
+ """
732
+
733
+ # FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
734
+ fontawesome_link = """
735
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
736
+ """
737
+
738
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
739
+ transformer = ImageTransformer()
740
+
741
+ def process_images(images, mode):
742
+ if not images:
743
+ return None, None, None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”."
744
+
745
+ results = []
746
+ all_selected_methods = []
747
+ all_similarities = []
748
+ method_details = [] # ๊ฐ ๋ณ€ํ˜•์˜ ์‹ค์ œ ์ˆ˜์น˜ ์ €์žฅ
749
+
750
+ # ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
751
+ with tempfile.TemporaryDirectory() as temp_dir:
752
+ for idx, image_data in enumerate(images):
753
+ # Gallery์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
754
+ if isinstance(image_data, tuple):
755
+ # Gallery๊ฐ€ ํŠœํ”Œ(ํŒŒ์ผ ๊ฒฝ๋กœ, ํŒŒ์ผ ์ด๋ฆ„) ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ
756
+ if len(image_data) > 0:
757
+ image_path = image_data[0] if isinstance(image_data[0], str) else image_data[1]
758
+ image = Image.open(image_path)
759
+ original_filename = os.path.basename(image_path)
760
+ else:
761
+ continue
762
+ elif isinstance(image_data, str): # ํŒŒ์ผ ๊ฒฝ๋กœ์ธ ๊ฒฝ์šฐ
763
+ image = Image.open(image_data)
764
+ original_filename = os.path.basename(image_data)
765
+ elif isinstance(image_data, dict) and 'name' in image_data: # ํŒŒ์ผ ์ •๋ณด ๋”•์…”๋„ˆ๋ฆฌ์ธ ๊ฒฝ์šฐ
766
+ image = Image.open(image_data['name'])
767
+ original_filename = os.path.basename(image_data['name'])
768
+ else: # PIL Image ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ
769
+ image = image_data
770
+ original_filename = f"image_{idx+1}.jpg"
771
+
772
+ # RGB๋กœ ๋ณ€ํ™˜ (RGBA ๋“ฑ ๋‹ค๋ฅธ ๋ชจ๋“œ ์ฒ˜๋ฆฌ)
773
+ if image.mode != 'RGB':
774
+ image = image.convert('RGB')
775
+
776
+ if mode == "๋žœ๋ค ๋ณ€ํ˜•":
777
+ # 95% ๋ชฉํ‘œ - ๊ฐ•๋„ 5
778
+ selected_methods = random.sample(list(transformer.method_info.keys()), 5)
779
+ intensity = 0.5
780
+ target_similarity = 95
781
+ else: # ์ตœ๋Œ€ ๋ณ€ํ˜•
782
+ # 90% ๋ชฉํ‘œ - ๋ชจ๋“  ๋ฐฉ๋ฒ• ์ ์šฉ, ๊ฐ•๋„ 10
783
+ selected_methods = list(transformer.method_info.keys())
784
+ intensity = 1.0
785
+ target_similarity = 90
786
+
787
+ transformed, details = transformer.transform_image_with_details(image, selected_methods, intensity)
788
+ similarity = transformer.calculate_similarity(image, transformed)
789
+
790
+ results.append(transformed)
791
+ all_selected_methods.append(selected_methods)
792
+ all_similarities.append(similarity)
793
+ method_details.append(details)
794
+
795
+ # ๋ณ€ํ˜•๋œ ์ด๋ฏธ์ง€ ์ €์žฅ - ๋ณ€๊ฒฝ_์›๋ž˜ํŒŒ์ผ๋ช…
796
+ name_part, ext = os.path.splitext(original_filename)
797
+ new_filename = f"๋ณ€๊ฒฝ_{name_part}{ext}"
798
+ transformed.save(os.path.join(temp_dir, new_filename), "JPEG", quality=95)
799
+
800
+ # ZIP ํŒŒ์ผ ์ƒ์„ฑ - ์ด๋ฏธ์ง€๋ณ€๊ฒฝ_ํ•œ๊ตญ๋‚ ์งœ_์‹œ๊ฐ„ ํ˜•์‹
801
+ kst = pytz.timezone('Asia/Seoul')
802
+ now_kst = datetime.datetime.now(kst)
803
+ zip_filename = f"์ด๋ฏธ์ง€๋ณ€๊ฒฝ_{now_kst.strftime('%y.%m.%d_%H.%M')}.zip"
804
+ zip_path = os.path.join(temp_dir, zip_filename)
805
+
806
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
807
+ for filename in os.listdir(temp_dir):
808
+ if filename.endswith(('.jpg', '.jpeg', '.png')):
809
+ zipf.write(os.path.join(temp_dir, filename), filename)
810
+
811
+ # ZIP ํŒŒ์ผ์„ ์‹ค์ œ๋กœ ์ €์žฅ
812
+ zip_output_path = os.path.join(tempfile.gettempdir(), zip_filename)
813
+ shutil.copy2(zip_path, zip_output_path)
814
+
815
+ # ๋ณ€ํ˜• ์„ค๋ช… ์ƒ์„ฑ
816
+ avg_similarity = sum(all_similarities) / len(all_similarities)
817
+
818
+ # ์ ์šฉ๋œ ๋ฐฉ๋ฒ• ์„ค๋ช… ์ƒ์„ฑ - ๊ธฐ๋ณธ ์„ค๋ช…๊ณผ ์ˆ˜์น˜๋ฅผ ํ•จ๊ป˜ ํฌํ•จ
819
+ if method_details:
820
+ detail_text = "### ์ ์šฉ๋œ ๋ณ€ํ˜• ๋ฐฉ๋ฒ•\n\n"
821
+ for method, detail in method_details[0].items():
822
+ # ๊ธฐ๋ณธ ์„ค๋ช…๊ณผ ์‹ค์ œ ์ ์šฉ ์ˆ˜์น˜๋ฅผ ๊ฒฐํ•ฉ
823
+ base_description = transformer.method_info[method]['description']
824
+ detail_text += f"**{method}**\n{base_description}\n์‹ค์ œ ์ ์šฉ ์ˆ˜์น˜: {detail}\n\n"
825
+
826
+ detail_text += f"\n**๋ณ€ํ˜• ๊ฒฐ๊ณผ**\n"
827
+ detail_text += f"- ์ฒ˜๋ฆฌ๋œ ์ด๋ฏธ์ง€ ์ˆ˜: {len(images)}๊ฐœ\n"
828
+ detail_text += f"- ํ‰๊ท  ์œ ์‚ฌ๋„: {avg_similarity:.2f}%\n"
829
+ detail_text += f"- ๋ชฉํ‘œ ์œ ์‚ฌ๋„: {target_similarity}%\n"
830
+ else:
831
+ detail_text = "๋ณ€ํ˜• ์ •๋ณด ์—†๏ฟฝ๏ฟฝ๏ฟฝ"
832
+
833
+ # ์„ ํƒ๋œ ๋ฐฉ๋ฒ•๋“ค (์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€ ๊ธฐ์ค€)
834
+ selected_methods_display = all_selected_methods[0]
835
+
836
+ return results, selected_methods_display, detail_text, zip_output_path
837
+
838
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ
839
+ def create_app():
840
+ with gr.Blocks(css=custom_css, title="๋„ค์ด๋ฒ„ ๋ธ”๋กœ๊ทธ ์œ ์‚ฌ ์ด๋ฏธ์ง€ ํšŒํ”ผ ๋„๊ตฌ", theme=gr.themes.Default(
841
+ primary_hue="orange",
842
+ secondary_hue="orange",
843
+ font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
844
+ )) as demo:
845
+ gr.HTML(fontawesome_link)
846
+
847
+ # ์ด๋ฏธ์ง€ ๋ณ€ํ˜• ๊ธฐ๋Šฅ ์„น์…˜ - ํƒญ ์—†์ด ์ง์ ‘ ๊ตฌํ˜„
848
+ with gr.Row():
849
+ with gr.Column(scale=1):
850
+ # ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์„น์…˜
851
+ with gr.Column(elem_classes="custom-frame"):
852
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3097/3097412.png"> ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ</div>')
853
+ input_images = gr.Gallery(
854
+ label="์›๋ณธ ์ด๋ฏธ์ง€",
855
+ columns=3,
856
+ height="400px",
857
+ allow_preview=True,
858
+ object_fit="contain",
859
+ type="filepath",
860
+ elem_id="input_gallery"
861
+ )
862
+
863
+ with gr.Column(scale=1):
864
+ # ๋ณ€ํ˜•๋œ ์ด๋ฏธ์ง€ ์„น์…˜
865
+ with gr.Column(elem_classes="custom-frame"):
866
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1375/1375106.png"> ๋ณ€ํ˜•๋œ ์ด๋ฏธ์ง€</div>')
867
+ output_images = gr.Gallery(
868
+ label="",
869
+ columns=3,
870
+ height="400px",
871
+ allow_preview=True,
872
+ object_fit="contain",
873
+ show_download_button=True,
874
+ elem_id="output_gallery"
875
+ )
876
+
877
+ # ๋ณ€ํ˜• ์˜ต์…˜ ๋ฐ ๊ฒฐ๊ณผ ์„น์…˜
878
+ with gr.Row():
879
+ with gr.Column(scale=1):
880
+ with gr.Column(elem_classes="custom-frame"):
881
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> ๋ณ€ํ˜• ์˜ต์…˜</div>')
882
+ with gr.Row():
883
+ random_btn = gr.Button("๐ŸŽฒ ๋žœ๋ค ๋ณ€ํ˜• (95% ๋ชฉํ‘œ)", elem_classes="custom-button")
884
+ max_btn = gr.Button("โšก ์ตœ๋Œ€ ๋ณ€ํ˜• (90% ๋ชฉํ‘œ)", elem_classes="custom-button")
885
+
886
+ with gr.Column(scale=1):
887
+ with gr.Column(elem_classes="custom-frame"):
888
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3153/3153376.png"> ๊ฒฐ๊ณผ ๋‹ค์šด๋กœ๋“œ</div>')
889
+ download_file = gr.File(label="ZIP ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ")
890
+
891
+ # ์ ์šฉ๋œ ๋ณ€ํ˜• ๋ฐฉ๋ฒ• ์ •๋ณด
892
+ with gr.Column(elem_classes="custom-frame"):
893
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> ์ ์šฉ๋œ ๋ณ€ํ˜• ์ •๋ณด</div>')
894
+
895
+ with gr.Row():
896
+ with gr.Column(scale=1):
897
+ method_checkboxes = gr.CheckboxGroup(
898
+ choices=list(transformer.method_info.keys()),
899
+ label="์ ์šฉ๋œ ๋ณ€ํ˜• ๋ฐฉ๋ฒ•",
900
+ interactive=False
901
+ )
902
+
903
+ with gr.Column(scale=1):
904
+ status_text = gr.Textbox(
905
+ label="๋ณ€ํ˜• ์ƒ์„ธ ์ •๋ณด",
906
+ interactive=False,
907
+ lines=12
908
+ )
909
+
910
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
911
+ def random_transform(images):
912
+ if not images:
913
+ return None, None, "์ด๋ฏธ์ง€๋ฅผ ๋จผ์ € ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", None
914
+ return process_images(images, "๋žœ๋ค ๋ณ€ํ˜•")
915
+
916
+ def max_transform(images):
917
+ if not images:
918
+ return None, None, "์ด๋ฏธ์ง€๋ฅผ ๋จผ์ € ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", None
919
+ return process_images(images, "์ตœ๋Œ€ ๋ณ€ํ˜•")
920
+
921
+ random_btn.click(
922
+ fn=random_transform,
923
+ inputs=[input_images],
924
+ outputs=[output_images, method_checkboxes, status_text, download_file]
925
+ )
926
+
927
+ max_btn.click(
928
+ fn=max_transform,
929
+ inputs=[input_images],
930
+ outputs=[output_images, method_checkboxes, status_text, download_file]
931
+ )
932
+
933
+ return demo
934
+
935
+ if __name__ == "__main__":
936
+ app = create_app()
937
+ app.queue(max_size=10) # ์š”์ฒญ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ ์„ค์ •
938
+ app.launch(share=False, inbrowser=True, width="100%")