Liky-yyy commited on
Commit
0c2607c
·
1 Parent(s): e5fbc97

initial commit

Browse files
Files changed (11) hide show
  1. .gitattributes +2 -0
  2. .gitignore +1 -0
  3. README copy.md +1 -0
  4. app.py +660 -0
  5. label.png +3 -0
  6. label2.jpg +3 -0
  7. leaderboard.json +0 -0
  8. practice.json +10 -0
  9. pyproject.toml +15 -0
  10. requirements.txt +63 -0
  11. uv.lock +0 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
37
+ *.jpg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .venv
README copy.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # 리드미
app.py ADDED
@@ -0,0 +1,660 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
+ import pandas as pd
5
+ import json
6
+ import os
7
+ import threading
8
+ import gc
9
+ from datetime import datetime
10
+ try:
11
+ from pyngrok import ngrok
12
+ NGROK_AVAILABLE = True
13
+ except ImportError:
14
+ NGROK_AVAILABLE = False
15
+ try:
16
+ from skimage.metrics import structural_similarity as ssim
17
+ from skimage.metrics import peak_signal_noise_ratio as psnr
18
+ except ImportError:
19
+ # scikit-image가 없는 경우 대체 방법 사용
20
+ def ssim(img1, img2):
21
+ # 간단한 MSE 기반 유사도 계산
22
+ mse = np.mean((img1 - img2) ** 2)
23
+ return 1 / (1 + mse / 1000)
24
+
25
+ def psnr(img1, img2):
26
+ mse = np.mean((img1 - img2) ** 2)
27
+ if mse == 0:
28
+ return 100
29
+ return 20 * np.log10(255.0 / np.sqrt(mse))
30
+ import io
31
+
32
+ class ImageSimilarityLeaderboard:
33
+ def __init__(self, reference_image_path="label2.jpg", data_file="leaderboard.json"):
34
+ self.reference_image_path = reference_image_path
35
+ self.data_file = data_file
36
+ self.admin_password = "9900" # 관리자 비밀번호
37
+ self.admin_authenticated = False # 관리자 인증 상태
38
+
39
+ # 메모리 최적화를 위한 캐시 및 락 (먼저 초기화)
40
+ self._ref_image_cache = None
41
+ self._cache_loaded = False
42
+ self._file_lock = threading.Lock() # 파일 I/O 동시성 제어
43
+ self._processing_lock = threading.Lock() # 처리 동시성 제어
44
+
45
+ # 락 초기화 후 데이터 로드
46
+ self.leaderboard_data = self.load_leaderboard()
47
+ self.last_modified = self.get_file_modified_time()
48
+
49
+ # macOS 호환성을 위한 경고 억제
50
+ import warnings
51
+ warnings.filterwarnings("ignore", category=UserWarning, module="cv2")
52
+
53
+ def load_leaderboard(self):
54
+ """리더보드 데이터를 로드합니다."""
55
+ with self._file_lock: # 파일 I/O 동시성 제어
56
+ if os.path.exists(self.data_file):
57
+ try:
58
+ with open(self.data_file, 'r', encoding='utf-8') as f:
59
+ return json.load(f)
60
+ except:
61
+ return []
62
+ return []
63
+
64
+ def get_file_modified_time(self):
65
+ """파일 수정 시간을 반환합니다."""
66
+ if os.path.exists(self.data_file):
67
+ return os.path.getmtime(self.data_file)
68
+ return 0
69
+
70
+ def save_leaderboard(self):
71
+ """리더보드 데이터를 저장합니다."""
72
+ with self._file_lock: # 파일 I/O 동시성 제어
73
+ with open(self.data_file, 'w', encoding='utf-8') as f:
74
+ json.dump(self.leaderboard_data, f, ensure_ascii=False, indent=2)
75
+ self.last_modified = self.get_file_modified_time()
76
+
77
+ def check_for_updates(self):
78
+ """리더보드 업데이트 확인"""
79
+ current_modified = self.get_file_modified_time()
80
+ if current_modified > self.last_modified:
81
+ self.leaderboard_data = self.load_leaderboard()
82
+ self.last_modified = current_modified
83
+ return True
84
+ return False
85
+
86
+ def _load_reference_image(self):
87
+ """참조 이미지를 한 번만 로드하고 캐시합니다."""
88
+ if not self._cache_loaded:
89
+ if os.path.exists(self.reference_image_path):
90
+ ref_image = cv2.imread(self.reference_image_path)
91
+ if ref_image is not None:
92
+ # macOS 메모리 최적화
93
+ if ref_image.shape[0] > 1024 or ref_image.shape[1] > 1024:
94
+ # 큰 이미지는 미리 리사이즈하여 메모리 사용량 감소
95
+ scale = min(1024 / ref_image.shape[0], 1024 / ref_image.shape[1])
96
+ if scale < 1:
97
+ new_width = int(ref_image.shape[1] * scale)
98
+ new_height = int(ref_image.shape[0] * scale)
99
+ ref_image = cv2.resize(ref_image, (new_width, new_height))
100
+
101
+ self._ref_image_cache = cv2.cvtColor(ref_image, cv2.COLOR_BGR2RGB)
102
+ self._cache_loaded = True
103
+ # 메모리 정리
104
+ del ref_image
105
+ gc.collect()
106
+ return self._ref_image_cache
107
+
108
+ def _get_memory_usage(self):
109
+ """현재 메모리 사용량을 반환합니다."""
110
+ try:
111
+ import psutil
112
+ process = psutil.Process()
113
+ memory_info = process.memory_info()
114
+ return memory_info.rss / 1024 / 1024 # MB 단위
115
+ except (ImportError, AttributeError):
116
+ # macOS나 다른 환경에서 psutil이 없거나 동작하지 않는 경우
117
+ try:
118
+ import resource
119
+ # getrusage를 통한 메모리 사용량 측정
120
+ usage = resource.getrusage(resource.RUSAGE_SELF)
121
+ return usage.ru_maxrss / 1024 # KB -> MB
122
+ except:
123
+ return 0
124
+
125
+ def calculate_similarity(self, image1, image2):
126
+ try:
127
+ # 1) 그레이스케일 변환
128
+ if image1.ndim == 3:
129
+ gray1 = cv2.cvtColor(image1, cv2.COLOR_RGB2GRAY)
130
+ else:
131
+ gray1 = image1.copy()
132
+
133
+ if image2.ndim == 3:
134
+ gray2 = cv2.cvtColor(image2, cv2.COLOR_RGB2GRAY)
135
+ else:
136
+ gray2 = image2.copy()
137
+
138
+ # 2) 각 이미지를 독립적으로 표준 크기로 리사이즈 (512x512)
139
+ target_size = (512, 512)
140
+
141
+ gray1 = cv2.resize(gray1, target_size, interpolation=cv2.INTER_LINEAR)
142
+ gray2 = cv2.resize(gray2, target_size, interpolation=cv2.INTER_LINEAR)
143
+
144
+ # 4) SSIM 계산 (구조적 유사도)
145
+ try:
146
+ ssim_score = ssim(gray1.astype(np.float32), gray2.astype(np.float32))
147
+ except:
148
+ # SSIM 계산 실패시 MSE 기반 대체
149
+ mse = np.mean((gray1.astype(np.float32) - gray2.astype(np.float32)) ** 2)
150
+ ssim_score = 1 / (1 + mse / 10000)
151
+
152
+ # 5) PSNR 계산 (픽셀 단위 유사도)
153
+ mse = np.mean((gray1.astype(np.float32) - gray2.astype(np.float32)) ** 2)
154
+ if mse == 0:
155
+ psnr_score = 1.0
156
+ else:
157
+ psnr_score = 20 * np.log10(255.0 / np.sqrt(mse))
158
+ # PSNR을 0-1 범위로 더 관대하게 정규화 (보통 PSNR은 20-40 범위)
159
+ psnr_score = min(psnr_score / 40.0, 1.0)
160
+
161
+ # 6) 히스토그램 유사도
162
+ hist1 = cv2.calcHist([gray1], [0], None, [256], [0, 256])
163
+ hist2 = cv2.calcHist([gray2], [0], None, [256], [0, 256])
164
+ hist_corr = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
165
+ hist_score = (hist_corr + 1) / 2 # -1~1 → 0~1
166
+
167
+ # 7) 최종 점수 계산 (더 단순하게)
168
+ final_score = (ssim_score * 0.7 + hist_score * 0.3) * 100
169
+
170
+ # 8) PSNR이 높으면 약간의 보너스
171
+ if psnr_score > 0.8:
172
+ bonus = min((psnr_score - 0.8) * 25, 5) # 최대 5점 보너스
173
+ final_score = min(final_score + bonus, 100)
174
+
175
+ return {
176
+ 'ssim': float(ssim_score),
177
+ 'psnr': float(psnr_score * 100), # 백분율로 변환
178
+ 'histogram': float(hist_score),
179
+ 'template': 0.0, # 사용하지 않음
180
+ 'edge': 0.0, # 사용하지 않음
181
+ 'phash': 0.0, # 사용하지 않음
182
+ 'final_score': float(final_score)
183
+ }
184
+ except Exception as e:
185
+ print(f"유사도 계산 오류: {e}")
186
+ return {'ssim':0.0,'psnr':0.0,'histogram':0.0,'template':0.0,'edge':0.0,'phash':0.0,'final_score':0.0}
187
+
188
+ def process_image(self, uploaded_image, username):
189
+ """업로드된 이미지를 처리하고 점수를 계산합니다."""
190
+ if uploaded_image is None:
191
+ return "📤 이미지를 업로드해주세요.", self.get_leaderboard_df()
192
+
193
+ # 사용자명 검증 제거 - 어떤 이름이든 허용
194
+ if not username or not username.strip():
195
+ return "❌ 사용자 이름을 입력해주세요.", self.get_leaderboard_df()
196
+
197
+ username = username.strip()
198
+
199
+ # 동시성 제어 - 한 번에 하나의 이미지만 처리
200
+ with self._processing_lock:
201
+ try:
202
+ # 캐시된 참조 이미지 사용
203
+ ref_image = self._load_reference_image()
204
+ if ref_image is None:
205
+ return f"참조 이미지({self.reference_image_path})를 로드할 수 없습니다.", None
206
+
207
+ # 업로드된 이미지를 numpy 배열로 변환
208
+ if isinstance(uploaded_image, str):
209
+ # 파일 경로인 경우
210
+ user_image = cv2.imread(uploaded_image)
211
+ if user_image is None:
212
+ return "업로드된 이미지를 읽을 수 없습니다.", None
213
+ user_image = cv2.cvtColor(user_image, cv2.COLOR_BGR2RGB)
214
+ else:
215
+ # PIL Image인 경우
216
+ user_image = np.array(uploaded_image)
217
+ if user_image is None or user_image.size == 0:
218
+ return "업로드된 이미지가 유효하지 않습니다.", None
219
+
220
+ # 이미지 크기 확인
221
+ if user_image.shape[0] < 10 or user_image.shape[1] < 10:
222
+ return "이미지가 너무 작습니다. 최소 10x10 픽셀 이상이어야 합니다.", None
223
+
224
+ # 유사도 계산
225
+ similarity_scores = self.calculate_similarity(ref_image, user_image)
226
+
227
+ # 메모리 정리 (macOS 최적화)
228
+ del user_image
229
+ if 'ref_image' in locals():
230
+ del ref_image
231
+ gc.collect()
232
+
233
+ # macOS에서 메모리 강제 정리
234
+ import platform
235
+ if platform.system() == 'Darwin': # macOS
236
+ import ctypes
237
+ try:
238
+ libc = ctypes.CDLL('libc.dylib')
239
+ libc.malloc_trim(0)
240
+ except:
241
+ pass
242
+
243
+ # 리더보드에 추가 (JSON 직렬화를 위해 float로 변환)
244
+ entry = {
245
+ 'username': username,
246
+ 'score': float(round(similarity_scores['final_score'], 2)),
247
+ 'date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
248
+ 'ssim': float(round(similarity_scores['ssim'], 4)),
249
+ 'psnr': float(round(similarity_scores['psnr'], 2)),
250
+ 'histogram': float(round(similarity_scores['histogram'], 4))
251
+ }
252
+
253
+ # 같은 이름의 기존 기록이 있는지 확인하고, 더 높은 점수만 유지
254
+ existing_indices = [i for i, data in enumerate(self.leaderboard_data) if data['username'] == username]
255
+
256
+ if existing_indices:
257
+ # 기존 기록 중 가장 높은 점수 찾기
258
+ existing_scores = [self.leaderboard_data[i]['score'] for i in existing_indices]
259
+ max_existing_score = max(existing_scores)
260
+
261
+ # 새 점수가 더 높으면 기존 기록들을 모두 제거하고 새 기록 추가
262
+ if entry['score'] > max_existing_score:
263
+ # 기존 기록들을 역순으로 제거 (인덱스 변경 방지)
264
+ for i in sorted(existing_indices, reverse=True):
265
+ del self.leaderboard_data[i]
266
+ self.leaderboard_data.append(entry)
267
+ self.save_leaderboard()
268
+
269
+ # 갱신된 경우의 메시지
270
+ result_message = f"""🎉 대단해요! 새로운 최고 기록입니다!
271
+
272
+ 👤 {username}님
273
+ 🏆 점수: {entry['score']:.0f}점
274
+ 📈 이전 최고: {max_existing_score:.0f}점
275
+
276
+ ✅ 리더보드에 등록되었습니다!
277
+ 📅 {entry['date']}"""
278
+ else:
279
+ # 새 점수가 더 낮거나 같으면 리더보드는 업데이트하지 않음
280
+ result_message = f"""🎯 점수가 계산되었습니다!
281
+
282
+ 👤 {username}님
283
+ 🏆 현재 점수: {entry['score']:.0f}점
284
+ 📈 최고 점수: {max_existing_score:.0f}점
285
+
286
+ 💪 조금만 더 노력하면 최고 기록을 갱신할 수 있을 것 같아요!
287
+ 다시 시도해보세요!"""
288
+ else:
289
+ # 기존 기록이 없으면 새로 추가
290
+ self.leaderboard_data.append(entry)
291
+ self.save_leaderboard()
292
+
293
+ # 새로 등록된 경우의 메시지
294
+ result_message = f"""🎉 첫 등록을 축하합니다!
295
+
296
+ 👤 {username}님
297
+ 🏆 점수: {entry['score']:.0f}점
298
+
299
+ ✅ 리더보드에 등록되었습니다!
300
+ 📅 {entry['date']}"""
301
+
302
+ return result_message, self.get_leaderboard_df()
303
+
304
+ except Exception as e:
305
+ import traceback
306
+ error_msg = f"오류가 발생했습니다: {str(e)}\n\n상세 정보:\n{traceback.format_exc()}"
307
+ return error_msg, None
308
+
309
+ def get_leaderboard_df(self):
310
+ """리더보드를 DataFrame으로 반환합니다."""
311
+ if not self.leaderboard_data:
312
+ return pd.DataFrame(columns=['순위', '사용자명', '점수', '날짜'])
313
+
314
+ # 점수 기준으로 정렬
315
+ sorted_data = sorted(self.leaderboard_data, key=lambda x: x['score'], reverse=True)
316
+
317
+ # DataFrame 생성
318
+ df_data = []
319
+ for i, entry in enumerate(sorted_data, 1):
320
+ df_data.append({
321
+ '순위': i,
322
+ '사용자명': entry['username'],
323
+ '점수': entry['score'],
324
+ '날짜': entry['date']
325
+ })
326
+
327
+ return pd.DataFrame(df_data)
328
+
329
+ def authenticate_admin(self, password):
330
+ """관리자 인증"""
331
+ if password == self.admin_password:
332
+ self.admin_authenticated = True
333
+ return "✅ 관리자 인증이 완료되었습니다.", True
334
+ else:
335
+ self.admin_authenticated = False
336
+ return "❌ 잘못된 비밀번호입니다.", False
337
+
338
+ def update_reference_image(self, new_image):
339
+ """참조 이미지 업데이트"""
340
+ if not self.admin_authenticated:
341
+ return "❌ 관리자 인증이 필요합니다."
342
+
343
+ try:
344
+ if new_image is None:
345
+ return "❌ 이미지를 업로���해주세요."
346
+
347
+ # 이미지를 저장
348
+ new_image.save(self.reference_image_path)
349
+ return f"✅ 참조 이미지가 업데이트되었습니다. ({self.reference_image_path})"
350
+ except Exception as e:
351
+ return f"❌ 이미지 저장 중 오류가 발생했습니다: {str(e)}"
352
+
353
+ def clear_leaderboard(self):
354
+ """리더보드를 초기화합니다."""
355
+ if not self.admin_authenticated:
356
+ return "❌ 관리자 인증이 필요합니다.", self.get_leaderboard_df()
357
+
358
+ self.leaderboard_data = []
359
+ self.save_leaderboard()
360
+ return "✅ 리더보드가 초기화되었습니다.", pd.DataFrame(columns=['순위', '사용자명', '점수', '날짜'])
361
+
362
+
363
+ # 전역 리더보드 인스턴스들 (4개 탭: 3개 반 + 연습)
364
+ leaderboards = {
365
+ 'respect': ImageSimilarityLeaderboard("label2.jpg", "respect.json"),
366
+ 'challenge': ImageSimilarityLeaderboard("label2.jpg", "challenge.json"),
367
+ 'originality': ImageSimilarityLeaderboard("label2.jpg", "originality.json"),
368
+ 'practice': ImageSimilarityLeaderboard("label2.jpg", "practice.json")
369
+ }
370
+
371
+ def process_user_image_respect(image, username):
372
+ """Respect반 사용자 이미지 처리 함수"""
373
+ return leaderboards['respect'].process_image(image, username)
374
+
375
+ def process_user_image_challenge(image, username):
376
+ """Challenge반 사용자 이미지 처리 함수"""
377
+ return leaderboards['challenge'].process_image(image, username)
378
+
379
+ def process_user_image_originality(image, username):
380
+ """Originality반 사용자 이미지 처리 함수"""
381
+ return leaderboards['originality'].process_image(image, username)
382
+
383
+ def get_current_leaderboard_respect():
384
+ """Respect반 현재 리더보드 반환"""
385
+ return leaderboards['respect'].get_leaderboard_df()
386
+
387
+ def get_current_leaderboard_challenge():
388
+ """Challenge반 현재 리더보드 반환"""
389
+ return leaderboards['challenge'].get_leaderboard_df()
390
+
391
+ def get_current_leaderboard_originality():
392
+ """Originality반 현재 리더보드 반환"""
393
+ return leaderboards['originality'].get_leaderboard_df()
394
+
395
+ def check_and_update_leaderboard_respect():
396
+ """Respect반 리더보드 업데이트 확인 및 반환"""
397
+ leaderboards['respect'].check_for_updates()
398
+ return leaderboards['respect'].get_leaderboard_df()
399
+
400
+ def check_and_update_leaderboard_challenge():
401
+ """Challenge반 리더보드 업데이트 확인 및 반환"""
402
+ leaderboards['challenge'].check_for_updates()
403
+ return leaderboards['challenge'].get_leaderboard_df()
404
+
405
+ def check_and_update_leaderboard_originality():
406
+ """Originality반 리더보드 업데이트 확인 및 반환"""
407
+ leaderboards['originality'].check_for_updates()
408
+ return leaderboards['originality'].get_leaderboard_df()
409
+
410
+ def process_user_image_practice(image, username):
411
+ """연습 탭 사용자 이미지 처리 함수"""
412
+ return leaderboards['practice'].process_image(image, username)
413
+
414
+ def get_current_leaderboard_practice():
415
+ """연습 탭 현재 리더보드 반환"""
416
+ return leaderboards['practice'].get_leaderboard_df()
417
+
418
+ def check_and_update_leaderboard_practice():
419
+ """연습 탭 리더보드 업데이트 확인 및 반환"""
420
+ leaderboards['practice'].check_for_updates()
421
+ return leaderboards['practice'].get_leaderboard_df()
422
+
423
+ def create_interface():
424
+ """Gradio 인터페이스 생성"""
425
+ with gr.Blocks(title="비슷한 이미지를 만들어주세요!", theme=gr.themes.Soft()) as demo:
426
+ gr.Markdown("""
427
+ # 🏆 참조 이미지와 얼마나 유사한지 측정하여 리더보드에 등록해보세요!
428
+ """)
429
+
430
+
431
+ # 탭 생성
432
+ with gr.Tabs():
433
+ # 연습 탭 (메인)
434
+ with gr.Tab("🎓 연습"):
435
+ gr.Markdown("### 💡 연습용 탭 (메인)")
436
+ gr.Markdown("업로드 및 이미지 유사도를 테스트해볼 수 있는 연습 공간입니다.")
437
+ gr.Markdown("---")
438
+
439
+ with gr.Row():
440
+ with gr.Column(scale=1):
441
+ gr.Markdown("### 📤 이미지 업로드 (연습)")
442
+ image_input_practice = gr.Image(
443
+ label="비교할 이미지를 업로드하세요",
444
+ type="pil",
445
+ height=300
446
+ )
447
+ username_input_practice = gr.Textbox(
448
+ label="사용자 이름 (연습용)",
449
+ placeholder="이름을 입력하세요",
450
+ max_lines=1
451
+ )
452
+ submit_btn_practice = gr.Button("🚀 유사도 테스트", variant="primary", size="lg")
453
+
454
+ with gr.Column(scale=1):
455
+ gr.Markdown("### 📊 테스트 결과")
456
+ result_output_practice = gr.Textbox(
457
+ label="유사도 분��� 결과",
458
+ lines=12,
459
+ interactive=False
460
+ )
461
+
462
+ gr.Markdown("### 🏅 연습 리더보드")
463
+ leaderboard_output_practice = gr.Dataframe(
464
+ headers=["순위", "사용자명", "점수", "날짜"],
465
+ datatype=["number", "str", "number", "str"],
466
+ interactive=False
467
+ )
468
+
469
+ # 연습 탭 이벤트 핸들러
470
+ submit_btn_practice.click(
471
+ fn=process_user_image_practice,
472
+ inputs=[image_input_practice, username_input_practice],
473
+ outputs=[result_output_practice, leaderboard_output_practice]
474
+ )
475
+
476
+ # Respect반 탭
477
+ with gr.Tab("🎯 Respect반"):
478
+ with gr.Row():
479
+ with gr.Column(scale=1):
480
+ gr.Markdown("### 📤 이미지 업로드 (Respect반)")
481
+ image_input_respect = gr.Image(
482
+ label="비교할 이미지를 업로드하세요",
483
+ type="pil",
484
+ height=300
485
+ )
486
+ username_input_respect = gr.Textbox(
487
+ label="사용자 이름",
488
+ placeholder="이름을 입력하세요",
489
+ max_lines=1
490
+ )
491
+ submit_btn_respect = gr.Button("🚀 점수 계산 및 등록", variant="primary", size="lg")
492
+
493
+ with gr.Column(scale=1):
494
+ gr.Markdown("### 📊 결과 (Respect반)")
495
+ result_output_respect = gr.Textbox(
496
+ label="계산 결과",
497
+ lines=10,
498
+ interactive=False
499
+ )
500
+
501
+ gr.Markdown("### 🏅 Respect반 리더보드")
502
+ leaderboard_output_respect = gr.Dataframe(
503
+ headers=["순위", "사용자명", "점수", "날짜"],
504
+ datatype=["number", "str", "number", "str"],
505
+ interactive=False
506
+ )
507
+
508
+ # Respect반 이벤트 핸들러
509
+ submit_btn_respect.click(
510
+ fn=process_user_image_respect,
511
+ inputs=[image_input_respect, username_input_respect],
512
+ outputs=[result_output_respect, leaderboard_output_respect]
513
+ )
514
+
515
+ # Challenge반 탭
516
+ with gr.Tab("⚡ Challenge반"):
517
+ with gr.Row():
518
+ with gr.Column(scale=1):
519
+ gr.Markdown("### 📤 이미지 업로드 (Challenge반)")
520
+ image_input_challenge = gr.Image(
521
+ label="비교할 이미지를 업로드하세요",
522
+ type="pil",
523
+ height=300
524
+ )
525
+ username_input_challenge = gr.Textbox(
526
+ label="사용자 이름",
527
+ placeholder="이름을 입력하세요",
528
+ max_lines=1
529
+ )
530
+ submit_btn_challenge = gr.Button("🚀 점수 계산 및 등록", variant="primary", size="lg")
531
+
532
+ with gr.Column(scale=1):
533
+ gr.Markdown("### 📊 결과 (Challenge반)")
534
+ result_output_challenge = gr.Textbox(
535
+ label="계산 결과",
536
+ lines=10,
537
+ interactive=False
538
+ )
539
+
540
+ gr.Markdown("### 🏅 Challenge반 리더보드")
541
+ leaderboard_output_challenge = gr.Dataframe(
542
+ headers=["순위", "사용자명", "점수", "날짜"],
543
+ datatype=["number", "str", "number", "str"],
544
+ interactive=False
545
+ )
546
+
547
+ # Challenge반 이벤트 핸들러
548
+ submit_btn_challenge.click(
549
+ fn=process_user_image_challenge,
550
+ inputs=[image_input_challenge, username_input_challenge],
551
+ outputs=[result_output_challenge, leaderboard_output_challenge]
552
+ )
553
+
554
+ # Originality반 탭
555
+ with gr.Tab("🎨 Originality반"):
556
+ with gr.Row():
557
+ with gr.Column(scale=1):
558
+ gr.Markdown("### 📤 이미지 업로드 (Originality반)")
559
+ image_input_originality = gr.Image(
560
+ label="비교할 이미지를 업로드하세요",
561
+ type="pil",
562
+ height=300
563
+ )
564
+ username_input_originality = gr.Textbox(
565
+ label="사용자 이름",
566
+ placeholder="이름을 입력하세요",
567
+ max_lines=1
568
+ )
569
+ submit_btn_originality = gr.Button("🚀 점수 계산 및 등록", variant="primary", size="lg")
570
+
571
+ with gr.Column(scale=1):
572
+ gr.Markdown("### 📊 결과 (Originality반)")
573
+ result_output_originality = gr.Textbox(
574
+ label="계산 결과",
575
+ lines=10,
576
+ interactive=False
577
+ )
578
+
579
+ gr.Markdown("### 🏅 Originality반 리더보드")
580
+ leaderboard_output_originality = gr.Dataframe(
581
+ headers=["순위", "사용자명", "점수", "날짜"],
582
+ datatype=["number", "str", "number", "str"],
583
+ interactive=False
584
+ )
585
+
586
+ # Originality반 이벤트 핸들러
587
+ submit_btn_originality.click(
588
+ fn=process_user_image_originality,
589
+ inputs=[image_input_originality, username_input_originality],
590
+ outputs=[result_output_originality, leaderboard_output_originality]
591
+ )
592
+
593
+ # 페이지 로드 시 모든 리더보드 표시
594
+ demo.load(
595
+ fn=lambda: (get_current_leaderboard_practice(), get_current_leaderboard_respect(), get_current_leaderboard_challenge(), get_current_leaderboard_originality()),
596
+ outputs=[leaderboard_output_practice, leaderboard_output_respect, leaderboard_output_challenge, leaderboard_output_originality]
597
+ )
598
+
599
+ # 실시간 업데이트 (10초마다 - 모든 탭 업데이트)
600
+ timer = gr.Timer(value=10)
601
+ timer.tick(
602
+ fn=lambda: (check_and_update_leaderboard_practice(), check_and_update_leaderboard_respect(), check_and_update_leaderboard_challenge(), check_and_update_leaderboard_originality()),
603
+ outputs=[leaderboard_output_practice, leaderboard_output_respect, leaderboard_output_challenge, leaderboard_output_originality]
604
+ )
605
+
606
+ return demo
607
+
608
+ def main():
609
+ """메인 함수"""
610
+ print("🏆 이미지 유사도 리더보드 시스템을 시작합니다...")
611
+ print("📁 참조 이미지: label2.jpg")
612
+ print("💾 데이터 파일:")
613
+ print(" • Respect반: respect.json")
614
+ print(" • Challenge반: challenge.json")
615
+ print(" • Originality반: originality.json")
616
+ print(" • 연습: practice.json")
617
+
618
+ # 각 반별 메모리 사용량 체크
619
+ for class_name, lb in leaderboards.items():
620
+ initial_memory = lb._get_memory_usage()
621
+ print(f"💾 {class_name} 초기 메모리 사용량: {initial_memory:.1f}MB")
622
+
623
+ if not os.path.exists("label2.jpg"):
624
+ print("⚠️ 경고: 참조 이미지(label2.jpg)를 찾을 수 없습니다!")
625
+ print(" label2.jpg 파일을 프로젝트 루트에 배치해주세요.")
626
+
627
+ # Gradio 인터페이스 생성 및 실행
628
+ demo = create_interface()
629
+ print("🚀 서버를 시작합니다...")
630
+ print("📊 최적화 사항:")
631
+ print(" • 참조 이미지 캐싱 활성화")
632
+ print(" • 이미지 크기 제한: 512x512")
633
+ print(" • 동시성 제어 활성화")
634
+ print(" • 메모리 정리 자동화")
635
+ print(" • 실시간 업데이트: 10초 간격")
636
+ print(" • 4개 탭별 독립 리더보드 운영 (3개 반 + 연습)")
637
+
638
+ # 외부 공유 우선 시도
639
+ print("\n🌐 외부 접근 시도 중...")
640
+ try:
641
+ demo.launch(
642
+ server_name="0.0.0.0",
643
+ server_port=9900,
644
+ share=True,
645
+ show_error=False, # 에러 표시 최소화
646
+ quiet=False # 로그 표시 (공유 링크 확인용)
647
+ )
648
+ except Exception as e:
649
+ print(f"❌ 외부 공유 실패: {e}")
650
+ print("🔄 로컬 네트워크 모드로 전환...")
651
+ demo.launch(
652
+ server_name="0.0.0.0",
653
+ server_port=9900,
654
+ share=False,
655
+ show_error=True,
656
+ quiet=False
657
+ )
658
+
659
+ if __name__ == "__main__":
660
+ main()
label.png ADDED

Git LFS Details

  • SHA256: 6100c107c299038ae52c58472ccc1aebf2a82e3ff47ff3c487ef45c51ce48903
  • Pointer size: 131 Bytes
  • Size of remote file: 163 kB
label2.jpg ADDED

Git LFS Details

  • SHA256: d43cfa9b4254e76622a4b96cbf7148f5f834cc3b1641d2268bfc2b931679c6e7
  • Pointer size: 131 Bytes
  • Size of remote file: 244 kB
leaderboard.json ADDED
File without changes
practice.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "username": "0조",
4
+ "score": 2.33,
5
+ "date": "2025-09-08 14:31:16",
6
+ "ssim": 0.0292,
7
+ "psnr": 2.91,
8
+ "histogram": 0.0
9
+ }
10
+ ]
pyproject.toml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "temp"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "gradio>=4.0.0",
9
+ "opencv-python-headless>=4.8.0",
10
+ "numpy>=1.24.0",
11
+ "Pillow>=10.0.0",
12
+ "scikit-image>=0.21.0",
13
+ "pandas>=2.0.0",
14
+ "pyngrok>=7.3.0",
15
+ ]
requirements.txt ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==24.1.0
2
+ annotated-types==0.7.0
3
+ anyio==4.10.0
4
+ brotli==1.1.0
5
+ certifi==2025.8.3
6
+ charset-normalizer==3.4.3
7
+ click==8.2.1
8
+ fastapi==0.116.1
9
+ ffmpy==0.6.1
10
+ filelock==3.19.1
11
+ fsspec==2025.9.0
12
+ gradio==5.44.1
13
+ gradio-client==1.12.1
14
+ groovy==0.1.2
15
+ h11==0.16.0
16
+ hf-xet==1.1.9
17
+ httpcore==1.0.9
18
+ httpx==0.28.1
19
+ huggingface-hub==0.34.4
20
+ idna==3.10
21
+ imageio==2.37.0
22
+ jinja2==3.1.6
23
+ lazy-loader==0.4
24
+ markdown-it-py==4.0.0
25
+ markupsafe==3.0.2
26
+ mdurl==0.1.2
27
+ networkx==3.5
28
+ numpy==2.3.2
29
+ opencv-python-headless==4.11.0.86
30
+ orjson==3.11.3
31
+ packaging==25.0
32
+ pandas==2.3.2
33
+ pillow==11.3.0
34
+ pydantic==2.11.7
35
+ pydantic-core==2.33.2
36
+ pydub==0.25.1
37
+ pygments==2.19.2
38
+ pyngrok==7.3.0
39
+ python-dateutil==2.9.0.post0
40
+ python-multipart==0.0.20
41
+ pytz==2025.2
42
+ pyyaml==6.0.2
43
+ requests==2.32.5
44
+ rich==14.1.0
45
+ ruff==0.12.12
46
+ safehttpx==0.1.6
47
+ scikit-image==0.25.2
48
+ scipy==1.16.1
49
+ semantic-version==2.10.0
50
+ shellingham==1.5.4
51
+ six==1.17.0
52
+ sniffio==1.3.1
53
+ starlette==0.47.3
54
+ tifffile==2025.8.28
55
+ tomlkit==0.13.3
56
+ tqdm==4.67.1
57
+ typer==0.17.4
58
+ typing-extensions==4.15.0
59
+ typing-inspection==0.4.1
60
+ tzdata==2025.2
61
+ urllib3==2.5.0
62
+ uvicorn==0.35.0
63
+ websockets==15.0.1
uv.lock ADDED
The diff for this file is too large to render. See raw diff