VOIDER commited on
Commit
8ffbf61
·
verified ·
1 Parent(s): 24404d4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +339 -195
app.py CHANGED
@@ -10,8 +10,12 @@ from huggingface_hub import hf_hub_download
10
  import pandas as pd
11
  import tempfile
12
  import shutil
 
 
 
 
 
13
 
14
- # Utility classes and functions from provided code
15
  class MLP(torch.nn.Module):
16
  def __init__(self, input_size, xcol='emb', ycol='avg_rating', batch_norm=True):
17
  super().__init__()
@@ -43,90 +47,100 @@ class MLP(torch.nn.Module):
43
  def forward(self, x):
44
  return self.layers(x)
45
 
46
-
47
  class WaifuScorer(object):
48
  def __init__(self, model_path=None, device='cuda', cache_dir=None, verbose=False):
49
  self.verbose = verbose
50
-
51
- # Import clip here to avoid global import
52
- import clip
53
-
54
- if model_path is None:
55
- model_path = "Eugeoter/waifu-scorer-v4-beta/model.pth"
56
- if self.verbose:
57
- print(f"model path not set, switch to default: `{model_path}`")
58
-
59
- # Download from HuggingFace if needed
60
- if not os.path.isfile(model_path):
61
- split = model_path.split("/")
62
- username, repo_id, model_name = split[-3], split[-2], split[-1]
63
- model_path = hf_hub_download(f"{username}/{repo_id}", model_name, cache_dir=cache_dir)
64
-
65
- print(f"Loading WaifuScorer model from `{model_path}`")
66
-
67
- # Load MLP model
68
- self.mlp = MLP(input_size=768)
69
- s = torch.load(model_path, map_location=device)
70
- self.mlp.load_state_dict(s)
71
- self.mlp.to(device)
72
-
73
- # Load CLIP model
74
- self.model2, self.preprocess = clip.load("ViT-L/14", device=device)
75
- self.device = device
76
- self.dtype = torch.float32
77
- self.mlp.eval()
 
 
 
 
 
78
 
79
  @torch.no_grad()
80
  def __call__(self, images):
 
 
 
81
  if isinstance(images, Image.Image):
82
  images = [images]
83
  n = len(images)
84
  if n == 1:
85
- images = images*2 # batch norm requires at least 2 samples
86
-
87
- # Preprocess and encode images
88
  image_tensors = [self.preprocess(img).unsqueeze(0) for img in images]
89
  image_batch = torch.cat(image_tensors).to(self.device)
90
  image_features = self.model2.encode_image(image_batch)
91
-
92
- # Normalize features
93
  l2 = image_features.norm(2, dim=-1, keepdim=True)
94
  l2[l2 == 0] = 1
95
  im_emb_arr = (image_features / l2).to(device=self.device, dtype=self.dtype)
96
-
97
- # Get predictions
98
  predictions = self.mlp(im_emb_arr)
99
  scores = predictions.clamp(0, 10).cpu().numpy().reshape(-1).tolist()
100
-
101
- # Return only the requested number of scores
102
- return scores[:n]
103
 
 
104
 
105
  def load_aesthetic_predictor_v2_5():
106
- # This is a simplified version that just downloads the model
107
- # The actual implementation would import and use aesthetic_predictor_v2_5
108
- # We'll simulate the model with a dummy implementation
109
-
110
- class AestheticPredictorV2_5:
111
  def __init__(self):
112
  print("Loading Aesthetic Predictor V2.5...")
113
- # In a real implementation, this would load the actual model
114
-
115
- def inference(self, image):
116
- # Simulate model prediction with a placeholder
117
- # This would be replaced with actual model inference in the full implementation
118
- # Use a random value between 1 and 10 for testing
119
- return np.random.uniform(1, 10)
120
-
121
- return AestheticPredictorV2_5()
 
 
 
 
 
 
 
 
 
 
122
 
 
 
 
123
 
124
  def load_anime_aesthetic_model():
125
  model_path = hf_hub_download(repo_id="skytnt/anime-aesthetic", filename="model.onnx")
126
  model = rt.InferenceSession(model_path, providers=['CPUExecutionProvider'])
127
  return model
128
 
129
-
130
  def predict_anime_aesthetic(img, model):
131
  img = np.array(img).astype(np.float32) / 255
132
  s = 768
@@ -140,224 +154,354 @@ def predict_anime_aesthetic(img, model):
140
  pred = model.run(None, {"img": img_input})[0].item()
141
  return pred
142
 
143
-
144
  class ImageEvaluationTool:
145
  def __init__(self):
146
  self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
147
  print(f"Using device: {self.device}")
148
-
149
- # Load all models
150
  print("Loading models... This may take some time.")
151
-
152
- # 1. Aesthetic Shadow
153
  print("Loading Aesthetic Shadow model...")
154
  self.aesthetic_shadow = pipeline("image-classification", model="NeoChen1024/aesthetic-shadow-v2-backup", device=self.device)
155
-
156
- try:
157
- # 2. Waifu Scorer (requires CLIP)
158
- print("Loading Waifu Scorer model...")
159
- self.waifu_scorer = WaifuScorer(device=self.device, verbose=True)
160
- except Exception as e:
161
- print(f"Error loading Waifu Scorer: {e}")
162
- self.waifu_scorer = None
163
-
164
- # 3. Aesthetic Predictor V2.5 (placeholder)
165
  print("Loading Aesthetic Predictor V2.5...")
166
  self.aesthetic_predictor_v2_5 = load_aesthetic_predictor_v2_5()
167
-
168
- # 4. Cafe Aesthetic models
169
- print("Loading Cafe Aesthetic models...")
170
- self.cafe_aesthetic = pipeline("image-classification", "cafeai/cafe_aesthetic")
171
- self.cafe_style = pipeline("image-classification", "cafeai/cafe_style")
172
- self.cafe_waifu = pipeline("image-classification", "cafeai/cafe_waifu")
173
-
174
- # 5. Anime Aesthetic
175
  print("Loading Anime Aesthetic model...")
176
  self.anime_aesthetic = load_anime_aesthetic_model()
177
-
178
  print("All models loaded successfully!")
179
-
180
- # Create temp directory for storing processed images
181
  self.temp_dir = tempfile.mkdtemp()
182
-
183
  def evaluate_image(self, image):
184
- """Evaluate a single image with all models"""
185
  results = {}
186
-
187
- # Convert to PIL Image if not already
188
  if not isinstance(image, Image.Image):
189
  image = Image.fromarray(image)
190
-
191
- # 1. Aesthetic Shadow
192
  try:
193
  shadow_result = self.aesthetic_shadow(images=[image])[0]
194
  hq_score = [p for p in shadow_result if p['label'] == 'hq'][0]['score']
195
- results['aesthetic_shadow'] = round(hq_score, 2)
 
 
196
  except Exception as e:
197
  print(f"Error in Aesthetic Shadow: {e}")
198
  results['aesthetic_shadow'] = None
199
-
200
- # 2. Waifu Scorer
201
- if self.waifu_scorer:
202
- try:
203
- waifu_score = self.waifu_scorer([image])[0]
204
- results['waifu_scorer'] = round(waifu_score, 2)
205
- except Exception as e:
206
- print(f"Error in Waifu Scorer: {e}")
207
- results['waifu_scorer'] = None
208
- else:
209
  results['waifu_scorer'] = None
210
-
211
- # 3. Aesthetic Predictor V2.5
212
  try:
213
  v2_5_score = self.aesthetic_predictor_v2_5.inference(image)
214
- results['aesthetic_predictor_v2_5'] = round(v2_5_score, 2)
 
 
215
  except Exception as e:
216
  print(f"Error in Aesthetic Predictor V2.5: {e}")
217
  results['aesthetic_predictor_v2_5'] = None
218
-
219
- # 4. Cafe Aesthetic
220
- try:
221
- cafe_aesthetic_result = self.cafe_aesthetic(image, top_k=2)
222
- cafe_aesthetic_score = {d["label"]: round(d["score"], 2) for d in cafe_aesthetic_result}
223
- results['cafe_aesthetic_good'] = cafe_aesthetic_score.get('good', 0)
224
- results['cafe_aesthetic_bad'] = cafe_aesthetic_score.get('bad', 0)
225
-
226
- cafe_style_result = self.cafe_style(image, top_k=1)
227
- results['cafe_style'] = cafe_style_result[0]["label"]
228
-
229
- cafe_waifu_result = self.cafe_waifu(image, top_k=1)
230
- results['cafe_waifu'] = cafe_waifu_result[0]["label"]
231
- except Exception as e:
232
- print(f"Error in Cafe Aesthetic: {e}")
233
- results['cafe_aesthetic_good'] = None
234
- results['cafe_aesthetic_bad'] = None
235
- results['cafe_style'] = None
236
- results['cafe_waifu'] = None
237
-
238
- # 5. Anime Aesthetic
239
  try:
240
  img_array = np.array(image)
241
  anime_score = predict_anime_aesthetic(img_array, self.anime_aesthetic)
242
- results['anime_aesthetic'] = round(anime_score, 2)
 
 
243
  except Exception as e:
244
  print(f"Error in Anime Aesthetic: {e}")
245
  results['anime_aesthetic'] = None
246
-
 
 
 
 
 
 
 
 
247
  return results
248
-
249
- def process_images(self, image_files):
250
- """Process multiple image files and return results"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  results = []
252
-
253
  for i, file_path in enumerate(image_files):
254
  try:
255
- # Open image
256
  img = Image.open(file_path).convert("RGB")
257
-
258
- # Get image evaluation results
259
  eval_results = self.evaluate_image(img)
260
-
261
- # Save a thumbnail for the results table
262
- thumbnail_path = os.path.join(self.temp_dir, f"thumbnail_{i}.jpg")
263
- img.thumbnail((200, 200))
264
- img.save(thumbnail_path)
265
-
266
- # Add file info and thumbnail path to results
267
  result = {
268
  'file_name': os.path.basename(file_path),
269
- 'thumbnail': thumbnail_path,
270
  **eval_results
271
  }
272
  results.append(result)
273
-
274
  except Exception as e:
275
  print(f"Error processing {file_path}: {e}")
276
-
 
 
 
 
 
 
 
 
 
 
277
  return results
278
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  def cleanup(self):
280
- """Clean up temporary files"""
281
  if os.path.exists(self.temp_dir):
282
  shutil.rmtree(self.temp_dir)
283
 
 
 
284
 
285
- # Create the Gradio interface
286
  def create_interface():
 
 
287
  evaluator = ImageEvaluationTool()
288
-
 
289
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
290
  gr.Markdown("""
291
  # Comprehensive Image Evaluation Tool
292
-
293
  Upload images to evaluate them using multiple aesthetic and quality prediction models:
294
-
295
- - **Aesthetic Shadow**: Evaluates high-quality vs low-quality images
296
  - **Waifu Scorer**: Rates anime/illustration quality from 0-10
297
- - **Aesthetic Predictor V2.5**: General aesthetic quality prediction
298
- - **Cafe Aesthetic**: Multiple models for style and quality analysis
299
- - **Anime Aesthetic**: Specific model for anime style images
300
-
301
- Upload multiple images to get a comprehensive evaluation table.
302
  """)
303
-
304
  with gr.Row():
305
  with gr.Column(scale=1):
306
  input_images = gr.Files(label="Upload Images")
 
307
  process_btn = gr.Button("Evaluate Images", variant="primary")
308
  clear_btn = gr.Button("Clear Results")
309
-
310
  with gr.Column(scale=2):
311
- output_gallery = gr.Gallery(label="Evaluated Images", columns=5, object_fit="contain")
312
- output_table = gr.Dataframe(label="Evaluation Results")
313
-
314
- def process_images(files):
315
- # Get file paths
316
  file_paths = [f.name for f in files]
317
-
318
- # Process images
319
- results = evaluator.process_images(file_paths)
320
-
321
- # Prepare gallery and table
322
- gallery_images = [{"image": r["thumbnail"], "label": f"{r['file_name']}"} for r in results]
323
-
324
- # Create DataFrame for the table
325
- table_data = []
326
- for r in results:
327
- table_data.append({
328
- "File Name": r["file_name"],
329
- "Aesthetic Shadow": r["aesthetic_shadow"],
330
- "Waifu Scorer": r["waifu_scorer"],
331
- "Aesthetic V2.5": r["aesthetic_predictor_v2_5"],
332
- "Cafe (Good)": r["cafe_aesthetic_good"],
333
- "Cafe (Bad)": r["cafe_aesthetic_bad"],
334
- "Cafe Style": r["cafe_style"],
335
- "Cafe Waifu": r["cafe_waifu"],
336
- "Anime Score": r["anime_aesthetic"]
337
- })
338
-
339
- df = pd.DataFrame(table_data)
340
- return gallery_images, df
341
-
 
 
 
 
 
 
 
 
342
  def clear_results():
343
- return None, None
344
-
345
- process_btn.click(process_images, inputs=[input_images], outputs=[output_gallery, output_table])
346
- clear_btn.click(clear_results, inputs=[], outputs=[output_gallery, output_table])
347
-
348
- # Cleanup when closing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  demo.load(lambda: None, inputs=None, outputs=None)
350
-
351
  gr.Markdown("""
352
  ### Notes
353
  - The evaluation may take some time depending on the number and size of images
354
  - For best results, use high-quality images
355
- - Scores are on different scales depending on the model
 
 
 
356
  """)
357
-
358
  return demo
359
 
360
- # Launch the interface
361
  if __name__ == "__main__":
362
  demo = create_interface()
363
  demo.queue().launch()
 
10
  import pandas as pd
11
  import tempfile
12
  import shutil
13
+ import base64
14
+ from io import BytesIO
15
+
16
+ # Import necessary function from aesthetic_predictor_v2_5
17
+ from aesthetic_predictor_v2_5 import convert_v2_5_from_siglip
18
 
 
19
  class MLP(torch.nn.Module):
20
  def __init__(self, input_size, xcol='emb', ycol='avg_rating', batch_norm=True):
21
  super().__init__()
 
47
  def forward(self, x):
48
  return self.layers(x)
49
 
 
50
  class WaifuScorer(object):
51
  def __init__(self, model_path=None, device='cuda', cache_dir=None, verbose=False):
52
  self.verbose = verbose
53
+
54
+ try:
55
+ import clip
56
+
57
+ if model_path is None:
58
+ model_path = "Eugeoter/waifu-scorer-v3/model.pth"
59
+ if self.verbose:
60
+ print(f"model path not set, switch to default: `{model_path}`")
61
+
62
+ if not os.path.isfile(model_path):
63
+ split = model_path.split("/")
64
+ username, repo_id, model_name = split[-3], split[-2], split[-1]
65
+ model_path = hf_hub_download(f"{username}/{repo_id}", model_name, cache_dir=cache_dir)
66
+
67
+ print(f"Loading WaifuScorer model from `{model_path}`")
68
+
69
+ self.mlp = MLP(input_size=768)
70
+ if model_path.endswith(".safetensors"):
71
+ from safetensors.torch import load_file
72
+ state_dict = load_file(model_path)
73
+ else:
74
+ state_dict = torch.load(model_path, map_location=device)
75
+ self.mlp.load_state_dict(state_dict)
76
+ self.mlp.to(device)
77
+
78
+ self.model2, self.preprocess = clip.load("ViT-L/14", device=device)
79
+ self.device = device
80
+ self.dtype = torch.float32
81
+ self.mlp.eval()
82
+ self.available = True
83
+ except Exception as e:
84
+ print(f"Unable to initialize WaifuScorer: {e}")
85
+ self.available = False
86
 
87
  @torch.no_grad()
88
  def __call__(self, images):
89
+ if not self.available:
90
+ return [None] * (1 if not isinstance(images, list) else len(images))
91
+
92
  if isinstance(images, Image.Image):
93
  images = [images]
94
  n = len(images)
95
  if n == 1:
96
+ images = images*2
97
+
 
98
  image_tensors = [self.preprocess(img).unsqueeze(0) for img in images]
99
  image_batch = torch.cat(image_tensors).to(self.device)
100
  image_features = self.model2.encode_image(image_batch)
101
+
 
102
  l2 = image_features.norm(2, dim=-1, keepdim=True)
103
  l2[l2 == 0] = 1
104
  im_emb_arr = (image_features / l2).to(device=self.device, dtype=self.dtype)
105
+
 
106
  predictions = self.mlp(im_emb_arr)
107
  scores = predictions.clamp(0, 10).cpu().numpy().reshape(-1).tolist()
 
 
 
108
 
109
+ return scores[:n]
110
 
111
  def load_aesthetic_predictor_v2_5():
112
+ class AestheticPredictorV2_5_Impl: # Renamed class to avoid confusion
 
 
 
 
113
  def __init__(self):
114
  print("Loading Aesthetic Predictor V2.5...")
115
+ self.model, self.preprocessor = convert_v2_5_from_siglip(
116
+ low_cpu_mem_usage=True,
117
+ trust_remote_code=True,
118
+ )
119
+ if torch.cuda.is_available():
120
+ self.model = self.model.to(torch.bfloat16).cuda()
121
+
122
+ def inference(self, image: Image.Image) -> float:
123
+ # preprocess image
124
+ pixel_values = self.preprocessor(
125
+ images=image.convert("RGB"), return_tensors="pt"
126
+ ).pixel_values
127
+
128
+ if torch.cuda.is_available():
129
+ pixel_values = pixel_values.to(torch.bfloat16).cuda()
130
+
131
+ # predict aesthetic score
132
+ with torch.inference_mode():
133
+ score = self.model(pixel_values).logits.squeeze().float().cpu().numpy()
134
 
135
+ return score
136
+
137
+ return AestheticPredictorV2_5_Impl() # Return an instance of the implementation class
138
 
139
  def load_anime_aesthetic_model():
140
  model_path = hf_hub_download(repo_id="skytnt/anime-aesthetic", filename="model.onnx")
141
  model = rt.InferenceSession(model_path, providers=['CPUExecutionProvider'])
142
  return model
143
 
 
144
  def predict_anime_aesthetic(img, model):
145
  img = np.array(img).astype(np.float32) / 255
146
  s = 768
 
154
  pred = model.run(None, {"img": img_input})[0].item()
155
  return pred
156
 
 
157
  class ImageEvaluationTool:
158
  def __init__(self):
159
  self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
160
  print(f"Using device: {self.device}")
161
+
 
162
  print("Loading models... This may take some time.")
163
+
 
164
  print("Loading Aesthetic Shadow model...")
165
  self.aesthetic_shadow = pipeline("image-classification", model="NeoChen1024/aesthetic-shadow-v2-backup", device=self.device)
166
+
167
+ print("Loading Waifu Scorer model...")
168
+ self.waifu_scorer = WaifuScorer(device=self.device, verbose=True)
169
+
 
 
 
 
 
 
170
  print("Loading Aesthetic Predictor V2.5...")
171
  self.aesthetic_predictor_v2_5 = load_aesthetic_predictor_v2_5()
172
+
 
 
 
 
 
 
 
173
  print("Loading Anime Aesthetic model...")
174
  self.anime_aesthetic = load_anime_aesthetic_model()
175
+
176
  print("All models loaded successfully!")
177
+
 
178
  self.temp_dir = tempfile.mkdtemp()
179
+
180
  def evaluate_image(self, image):
 
181
  results = {}
182
+
 
183
  if not isinstance(image, Image.Image):
184
  image = Image.fromarray(image)
185
+
 
186
  try:
187
  shadow_result = self.aesthetic_shadow(images=[image])[0]
188
  hq_score = [p for p in shadow_result if p['label'] == 'hq'][0]['score']
189
+ # Scale aesthetic_shadow to 0-10 and clamp
190
+ aesthetic_shadow_score = np.clip(hq_score * 10.0, 0.0, 10.0)
191
+ results['aesthetic_shadow'] = aesthetic_shadow_score
192
  except Exception as e:
193
  print(f"Error in Aesthetic Shadow: {e}")
194
  results['aesthetic_shadow'] = None
195
+
196
+ try:
197
+ waifu_score = self.waifu_scorer([image])[0]
198
+ # Clamp waifu_score
199
+ waifu_score_clamped = np.clip(waifu_score, 0.0, 10.0)
200
+ results['waifu_scorer'] = waifu_score_clamped
201
+ except Exception as e:
202
+ print(f"Error in Waifu Scorer: {e}")
 
 
203
  results['waifu_scorer'] = None
204
+
 
205
  try:
206
  v2_5_score = self.aesthetic_predictor_v2_5.inference(image)
207
+ # Clamp v2.5 score
208
+ v2_5_score_clamped = np.clip(v2_5_score, 0.0, 10.0)
209
+ results['aesthetic_predictor_v2_5'] = float(np.round(v2_5_score_clamped, 4)) # Keep 4 decimal places after clamping
210
  except Exception as e:
211
  print(f"Error in Aesthetic Predictor V2.5: {e}")
212
  results['aesthetic_predictor_v2_5'] = None
213
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  try:
215
  img_array = np.array(image)
216
  anime_score = predict_anime_aesthetic(img_array, self.anime_aesthetic)
217
+ # Scale Anime Score to 0-10 and clamp
218
+ anime_score_scaled = np.clip(anime_score * 10.0, 0.0, 10.0)
219
+ results['anime_aesthetic'] = anime_score_scaled
220
  except Exception as e:
221
  print(f"Error in Anime Aesthetic: {e}")
222
  results['anime_aesthetic'] = None
223
+
224
+ # Calculate Final Score (simple average of available scores)
225
+ valid_scores = [v for v in results.values() if v is not None]
226
+ if valid_scores:
227
+ final_score = np.mean(valid_scores)
228
+ results['final_score'] = np.clip(final_score, 0.0, 10.0) # Clamp final score too
229
+ else:
230
+ results['final_score'] = None
231
+
232
  return results
233
+
234
+ def image_to_base64(self, image):
235
+ buffered = BytesIO()
236
+ image.save(buffered, format="JPEG")
237
+ return base64.b64encode(buffered.getvalue()).decode('utf-8')
238
+
239
+ def process_single_image(self, file_path):
240
+ try:
241
+ img = Image.open(file_path).convert("RGB")
242
+ eval_results = self.evaluate_image(img)
243
+ thumbnail = img.copy()
244
+ thumbnail.thumbnail((200, 200))
245
+ img_base64 = self.image_to_base64(thumbnail)
246
+ result = {
247
+ 'file_name': os.path.basename(file_path),
248
+ 'img_data': img_base64,
249
+ **eval_results
250
+ }
251
+ return result
252
+ except Exception as e:
253
+ print(f"Error processing {file_path}: {e}")
254
+ return None
255
+
256
+ def process_images_evaluation(self, image_files): # Renamed and now for evaluation only
257
  results = []
258
+
259
  for i, file_path in enumerate(image_files):
260
  try:
 
261
  img = Image.open(file_path).convert("RGB")
 
 
262
  eval_results = self.evaluate_image(img)
263
+
264
+ thumbnail = img.copy()
265
+ thumbnail.thumbnail((200, 200))
266
+
267
+ img_base64 = self.image_to_base64(thumbnail)
268
+
 
269
  result = {
270
  'file_name': os.path.basename(file_path),
271
+ 'img_data': img_base64,
272
  **eval_results
273
  }
274
  results.append(result)
275
+
276
  except Exception as e:
277
  print(f"Error processing {file_path}: {e}")
278
+
279
+ return results
280
+
281
+ def sort_results(self, results, sort_by="Final Score"): # New function for sorting
282
+ def sort_key(res): # Define a sorting key function
283
+ sort_value = res.get(sort_by.lower().replace(" ", "_"), None) # Handle spaces and case
284
+ if sort_value is None: # Put N/A at the end
285
+ return -float('inf') if sort_by == "File Name" else float('inf') # File Name sort N/A at end alphabetically
286
+ return sort_value
287
+
288
+ results.sort(key=sort_key, reverse=sort_by != "File Name") # Sort results, reverse for score columns
289
  return results
290
+
291
+ def generate_html_table(self, results):
292
+ html = """
293
+ <style>
294
+ .results-table {
295
+ width: 100%;
296
+ border-collapse: collapse;
297
+ margin: 20px 0;
298
+ font-family: Arial, sans-serif;
299
+ background-color: transparent;
300
+ }
301
+
302
+ .results-table th,
303
+ .results-table td {
304
+ color: #eee;
305
+ border: 1px solid #ddd;
306
+ padding: 8px;
307
+ text-align: center;
308
+ background-color: transparent;
309
+ }
310
+
311
+ .results-table th {
312
+ font-weight: bold;
313
+ }
314
+
315
+ .results-table tr:nth-child(even) {
316
+ background-color: transparent;
317
+ }
318
+
319
+ .results-table tr:hover {
320
+ background-color: rgba(255, 255, 255, 0.1);
321
+ }
322
+
323
+ .image-preview {
324
+ max-width: 150px;
325
+ max-height: 150px;
326
+ display: block;
327
+ margin: 0 auto;
328
+ }
329
+
330
+ .good-score {
331
+ color: #0f0;
332
+ font-weight: bold;
333
+ }
334
+ .bad-score {
335
+ color: #f00;
336
+ font-weight: bold;
337
+ }
338
+ .medium-score {
339
+ color: orange;
340
+ font-weight: bold;
341
+ }
342
+ </style>
343
+
344
+ <table class="results-table">
345
+ <thead>
346
+ <tr>
347
+ <th>Image</th>
348
+ <th>File Name</th>
349
+ <th>Aesthetic Shadow</th>
350
+ <th>Waifu Scorer</th>
351
+ <th>Aesthetic V2.5</th>
352
+ <th>Anime Score</th>
353
+ <th>Final Score</th>
354
+ </tr>
355
+ </thead>
356
+ <tbody>
357
+ """
358
+
359
+ for result in results:
360
+ html += "<tr>"
361
+ html += f'<td><img src="data:image/jpeg;base64,{result["img_data"]}" class="image-preview"></td>'
362
+ html += f'<td>{result["file_name"]}</td>'
363
+
364
+ score = result["aesthetic_shadow"]
365
+ score_class = "good-score" if score and score >= 7 else "medium-score" if score and score >= 4 else "bad-score"
366
+ html += f'<td class="{score_class}">{score if score is not None else "N/A":.4f}</td>' # Format to 4 decimal places
367
+
368
+ score = result["waifu_scorer"]
369
+ score_class = "good-score" if score and score >= 7 else "medium-score" if score and score >= 5 else "bad-score"
370
+ html += f'<td class="{score_class}">{score if score is not None else "N/A":.4f}</td>' # Format to 4 decimal places
371
+
372
+ score = result["aesthetic_predictor_v2_5"]
373
+ score_class = "good-score" if score and score >= 7 else "medium-score" if score and score >= 5 else "bad-score"
374
+ html += f'<td class="{score_class}">{score if score is not None else "N/A":.4f}</td>' # Format to 4 decimal places
375
+
376
+ score = result["anime_aesthetic"]
377
+ score_class = "good-score" if score and score >= 7 else "medium-score" if score and score >= 5 else "bad-score"
378
+ html += f'<td class="{score_class}">{score if score is not None else "N/A":.4f}</td>' # Format to 4 decimal places
379
+
380
+ score = result["final_score"]
381
+ score_class = "good-score" if score and score >= 7 else "medium-score" if score and score >= 5 else "bad-score"
382
+ html += f'<td class="{score_class}">{score if score is not None else "N/A":.4f}</td>' # Format to 4 decimal places
383
+
384
+
385
+ html += "</tr>"
386
+
387
+ html += """
388
+ </tbody>
389
+ </table>
390
+ """
391
+
392
+ return html
393
+
394
  def cleanup(self):
 
395
  if os.path.exists(self.temp_dir):
396
  shutil.rmtree(self.temp_dir)
397
 
398
+ # Global variable to store evaluation results
399
+ global_results = None
400
 
 
401
  def create_interface():
402
+ global global_results # Use the global variable
403
+
404
  evaluator = ImageEvaluationTool()
405
+ sort_options = ["Final Score", "File Name", "Aesthetic Shadow", "Waifu Scorer", "Aesthetic V2.5", "Anime Score"] # Sort options
406
+
407
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
408
  gr.Markdown("""
409
  # Comprehensive Image Evaluation Tool
410
+
411
  Upload images to evaluate them using multiple aesthetic and quality prediction models:
412
+
413
+ - **Aesthetic Shadow**: Evaluates high-quality vs low-quality images (scaled to 0-10)
414
  - **Waifu Scorer**: Rates anime/illustration quality from 0-10
415
+ - **Aesthetic Predictor V2.5**: General aesthetic quality prediction (clamped to 0-10)
416
+ - **Anime Aesthetic**: Specific model for anime style images (scaled and clamped to 0-10)
417
+ - **Final Score**: Average of available scores (clamped to 0-10)
418
+
419
+ Upload multiple images to get a comprehensive evaluation table. Scores are clamped to the range 0.0000 - 10.0000.
420
  """)
421
+
422
  with gr.Row():
423
  with gr.Column(scale=1):
424
  input_images = gr.Files(label="Upload Images")
425
+ sort_dropdown = gr.Dropdown(sort_options, value="Final Score", label="Sort by") # Dropdown for sorting
426
  process_btn = gr.Button("Evaluate Images", variant="primary")
427
  clear_btn = gr.Button("Clear Results")
428
+
429
  with gr.Column(scale=2):
430
+ progress_html = gr.HTML(label="Progress") # Keep progress_html if you want to show initial progress
431
+ output_html = gr.HTML(label="Evaluation Results")
432
+
433
+ def process_images_and_update(files): # Renamed and simplified
434
+ global global_results
435
  file_paths = [f.name for f in files]
436
+ total = len(file_paths)
437
+ progress_html_content = "" # Initialize progress content
438
+
439
+ if not file_paths: # Handle no files uploaded
440
+ global_results = []
441
+ return progress_html_content, evaluator.generate_html_table([]) # Empty table
442
+
443
+ progress_html_content = ""
444
+ for i, file_path in enumerate(file_paths):
445
+ percent = (i / total) * 100
446
+ progress_bar = f"""
447
+ <div>
448
+ <p>Processing {os.path.basename(file_path)}</p>
449
+ <progress value="{percent}" max="100"></progress>
450
+ <p>{percent:.1f}% complete</p>
451
+ </div>
452
+ """
453
+ progress_html_content = progress_bar # Update progress content
454
+ yield progress_html_content, gr.update() # Yield progress update
455
+ # No need to process and sort here, just evaluate
456
+ global_results = evaluator.process_images_evaluation(file_paths) # Evaluate all images and store
457
+ sorted_results = evaluator.sort_results(global_results, sort_by="Final Score") # Initial sort by Final Score
458
+ html_table = evaluator.generate_html_table(sorted_results)
459
+ yield "<p>Processing complete</p>", html_table # Final progress and table
460
+
461
+ def update_table_sort(sort_by_column): # New function for sorting update
462
+ global global_results
463
+ if global_results is None:
464
+ return "No images evaluated yet." # Or handle case when no images are evaluated
465
+ sorted_results = evaluator.sort_results(global_results, sort_by=sort_by_column)
466
+ html_table = evaluator.generate_html_table(sorted_results)
467
+ return html_table
468
+
469
  def clear_results():
470
+ global global_results
471
+ global_results = None # Clear stored results
472
+ return gr.update(value=""), gr.update(value="")
473
+
474
+
475
+ process_btn.click(
476
+ process_images_and_update,
477
+ inputs=[input_images],
478
+ outputs=[progress_html, output_html]
479
+ )
480
+ sort_dropdown.change( # Only update table on sort change
481
+ update_table_sort,
482
+ inputs=[sort_dropdown],
483
+ outputs=[output_html] # Only update output_html
484
+ )
485
+ clear_btn.click(
486
+ clear_results,
487
+ inputs=[],
488
+ outputs=[progress_html, output_html]
489
+ )
490
+
491
  demo.load(lambda: None, inputs=None, outputs=None)
492
+
493
  gr.Markdown("""
494
  ### Notes
495
  - The evaluation may take some time depending on the number and size of images
496
  - For best results, use high-quality images
497
+ - Scores are color-coded: green for good (>=7), orange for medium (>=5), and red for poor scores (<5, or <4 for Aesthetic Shadow)
498
+ - Some models may fail for certain image types, shown as "N/A" in the results
499
+ - "Final Score" is a simple average of available model scores.
500
+ - Table is sortable by clicking the dropdown above the "Evaluate Images" button. Default sort is by "Final Score". Sorting happens instantly without re-evaluating images.
501
  """)
502
+
503
  return demo
504
 
 
505
  if __name__ == "__main__":
506
  demo = create_interface()
507
  demo.queue().launch()