miachiquier commited on
Commit
0a41a0a
·
verified ·
1 Parent(s): eb96ac0

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1609
app.py DELETED
@@ -1,1609 +0,0 @@
1
- import PIL
2
- import math
3
- import torch
4
- import random
5
- import os
6
- import numpy as np
7
- import pandas as pd
8
- import gradio as gr
9
- import threading
10
- import time
11
- import zipfile
12
- import shutil
13
- import glob
14
- from pathlib import Path
15
- from torch.utils.data import DataLoader, Dataset, random_split
16
- import torchvision.transforms.v2 as transforms
17
- from diffusers.schedulers import DDIMScheduler, DDPMScheduler
18
- from peft import PeftModel, LoraConfig, get_peft_model
19
- from torchvision.transforms.functional import to_pil_image
20
- from PIL import Image, ImageDraw, ImageFont
21
- import imageio
22
- from tqdm import tqdm
23
- from torchvision.models.mobilenetv2 import MobileNet_V2_Weights
24
- from torchvision.models.resnet import ResNet18_Weights
25
- from torchvision.models.efficientnet import EfficientNet_B0_Weights
26
- from torchvision import models
27
- import torch.nn as nn
28
- from utils.metrics import ensemble_predict
29
- from diffusers import UNet2DConditionModel, VQModel
30
- from accelerate import Accelerator
31
- from accelerate.utils import set_seed
32
- from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection
33
- from torch.nn import functional as F
34
- from diffusers.optimization import get_scheduler
35
- import copy
36
- import subprocess
37
- import tarfile
38
- import queue
39
- import hashlib
40
- import json
41
- from models.kandinsky_pipelines import KandinskyV22PipelineWithInversion, ManipulateMode
42
-
43
-
44
- # Set seeds for reproducibility
45
- torch.manual_seed(0)
46
- random.seed(0)
47
- np.random.seed(0)
48
-
49
- # Define constants
50
- IMG_SIZE = 512
51
- BATCH_SIZE = 32
52
- DEVICE = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")
53
-
54
- # Add these global variables after the imports and before the CSS definition
55
- # Global variables for LoRA training
56
- lora_status = "Ready"
57
- lora_is_processing = False
58
-
59
- # Global variables for generation control
60
- generation_should_stop = False
61
- classifier_should_stop = False # New flag for classifier training
62
- embedding_should_stop = False # New flag for embedding encoding
63
- lora_should_stop = False # New flag for LoRA training
64
- generation_queue = queue.Queue()
65
- is_processing = False # Add this to prevent multiple simultaneous processes
66
-
67
- # Create temporary directories for uploads
68
- temp_dir = Path("./temp_uploads")
69
- temp_dir.mkdir(exist_ok=True, parents=True)
70
-
71
- lora_temp_dir = Path("./temp_lora_uploads")
72
- lora_temp_dir.mkdir(exist_ok=True, parents=True)
73
-
74
- # Create a global queue for real-time updates
75
- result_queue = queue.Queue()
76
- displayed_results = [] # Keep track of all displayed results
77
-
78
- # Add these global variables at the top of your file
79
- total_images_to_process = 0
80
- images_processed = 0
81
-
82
- # Add these global variables after the existing ones
83
- displayed_results_class0_to_class1 = [] # Results for class 0 to class 1
84
- displayed_results_class1_to_class0 = [] # Results for class 1 to class 0
85
-
86
- # Add global variables for caching
87
- CACHE_DIR = Path("./cached_results")
88
- CACHE_DIR.mkdir(exist_ok=True, parents=True)
89
-
90
- # CSS for styling the interface
91
- css = """
92
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
93
-
94
- body, * {
95
- font-family: 'Inter', sans-serif !important;
96
- letter-spacing: -0.01em;
97
- }
98
-
99
- .container {
100
- max-width: 1360px;
101
- margin: auto;
102
- padding-top: 2.5rem;
103
- padding-bottom: 2.5rem;
104
- }
105
-
106
- .header {
107
- text-align: center;
108
- margin-bottom: 3rem;
109
- padding-bottom: 2rem;
110
- border-bottom: 1px solid #f0f0f0;
111
- }
112
-
113
- .header h1 {
114
- font-size: 3rem;
115
- font-weight: 700;
116
- color: #222;
117
- letter-spacing: -0.03em;
118
- margin-bottom: 1rem;
119
- background: linear-gradient(90deg, #B39CD0 0%, #9D8AC7 100%);
120
- -webkit-background-clip: text;
121
- -webkit-text-fill-color: transparent;
122
- display: inline-block;
123
- }
124
-
125
- .header p {
126
- font-size: 1.1rem;
127
- color: #333;
128
- max-width: 800px;
129
- margin: 0 auto;
130
- line-height: 1.6;
131
- }
132
-
133
- .subtitle {
134
- font-size: 0.95rem;
135
- color: #777;
136
- max-width: 800px;
137
- margin: 0.5rem auto 0;
138
- line-height: 1.5;
139
- }
140
-
141
- .contact-info {
142
- font-size: 0.8rem;
143
- color: #777;
144
- margin-top: 15px;
145
- padding-top: 10px;
146
- border-top: 1px dashed #e0e0e0;
147
- width: 80%;
148
- margin-left: auto;
149
- margin-right: auto;
150
- }
151
-
152
- .paper-info {
153
- background-color: #f8f9fa;
154
- border-radius: 12px;
155
- padding: 1.8rem;
156
- margin: 1.8rem 0;
157
- box-shadow: 0 6px 20px rgba(0,0,0,0.05);
158
- border-left: 4px solid #B39CD0;
159
- }
160
-
161
- .paper-info h3 {
162
- font-size: 1.5rem;
163
- font-weight: 600;
164
- color: #B39CD0;
165
- letter-spacing: -0.02em;
166
- margin-bottom: 1rem;
167
- }
168
-
169
- .paper-info p {
170
- font-size: 1.05em;
171
- line-height: 1.7;
172
- color: #333;
173
- }
174
-
175
- .section-header {
176
- font-size: 1.8rem;
177
- font-weight: 600;
178
- color: #B39CD0;
179
- margin: 2.5rem 0 1.5rem 0;
180
- padding-bottom: 0.8rem;
181
- border-bottom: 2px solid #ECF0F1;
182
- letter-spacing: -0.02em;
183
- }
184
-
185
- .footer {
186
- text-align: center;
187
- margin-top: 3rem;
188
- padding: 1.5rem;
189
- border-top: 1px solid #ECF0F1;
190
- color: #666;
191
- background-color: #f8f9fa;
192
- border-radius: 0 0 12px 12px;
193
- }
194
-
195
- .btn-primary {
196
- background-color: #B39CD0 !important;
197
- border-color: #B39CD0 !important;
198
- transition: all 0.3s ease;
199
- font-weight: 500 !important;
200
- letter-spacing: 0.02em !important;
201
- padding: 0.6rem 1.5rem !important;
202
- border-radius: 8px !important;
203
- }
204
-
205
- .btn-primary:hover {
206
- background-color: #9D8AC7 !important;
207
- border-color: #9D8AC7 !important;
208
- }
209
-
210
- /* Hide the output directory */
211
- .hidden-element {
212
- display: none !important;
213
- }
214
-
215
- /* Additional CSS for better alignment */
216
- .container {
217
- padding: 0 1.5rem;
218
- }
219
-
220
- .main-container {
221
- display: flex;
222
- flex-direction: column;
223
- gap: 1.5rem;
224
- }
225
-
226
- .results-container {
227
- margin-top: 0;
228
- padding-top: 0;
229
- }
230
-
231
- .full-width-header {
232
- margin-bottom: 2rem;
233
- padding-bottom: 1.5rem;
234
- border-bottom: 1px solid #f0f0f0;
235
- text-align: center;
236
- }
237
-
238
- .content-row {
239
- display: flex;
240
- gap: 2rem;
241
- }
242
-
243
- .sidebar {
244
- min-width: 250px;
245
- padding-right: 1.5rem;
246
- }
247
-
248
- .section-header {
249
- margin-top: 0;
250
- }
251
-
252
- .tabs-container {
253
- margin-top: 1rem;
254
- }
255
-
256
- .gallery-container {
257
- margin-top: 1rem;
258
- }
259
-
260
- /* Hide the output directory */
261
- .hidden-element {
262
- display: none !important;
263
- }
264
-
265
- .gallery-item img {
266
- object-fit: contain !important;
267
- height: 200px !important;
268
- width: auto !important;
269
- }
270
-
271
- /* Force GIFs to restart when tab is selected */
272
- .tabs-container .tabitem[style*="display: block"] .gallery-container img {
273
- animation: none;
274
- animation: reload-animation 0.1s;
275
- }
276
-
277
- @keyframes reload-animation {
278
- 0% { opacity: 0.99; }
279
- 100% { opacity: 1; }
280
- }
281
- """
282
-
283
- # Add to your global variables
284
- current_cache_key = None
285
- is_using_default_params = False
286
-
287
- # Update the EXAMPLE_DATASETS to include direct dataset paths, embeddings, and classifiers
288
- EXAMPLE_DATASETS = [
289
- {
290
- "name": "butterfly",
291
- "display_name": "Butterfly (Monarch vs Viceroy)",
292
- "description": "Dataset containing images of Monarch and Viceroy butterflies for counterfactual generation",
293
- "path": "/proj/vondrick/datasets/magnification/butterfly.tar.gz",
294
- "direct_dataset_path": "/proj/vondrick2/mia/diff-usion/example_images/butterfly",
295
- "checkpoint_path": "/proj/vondrick2/mia/magnificationold/output/lora/butterfly/copper-forest-49/checkpoint-1800",
296
- "embeddings_path": "/proj/vondrick2/mia/diff-usion/results/clip_image_embeds/butterfly",
297
- "classifier_path": "/proj/vondrick2/mia/diff-usion/results/ensemble/butterfly",
298
- "class_names": ["class0", "class1"]
299
- },
300
- {
301
- "name": "afhq",
302
- "display_name": "Cats vs. Dogs (AFHQ)",
303
- "description": "Dataset containing images of table lamps and floor lamps",
304
- "direct_dataset_path": "/proj/vondrick2/mia/diff-usion/example_images/afhq",
305
- "checkpoint_path": None,
306
- "embeddings_path": "/proj/vondrick2/mia/diff-usion/results/clip_image_embeds/afhq",
307
- "classifier_path": "/proj/vondrick2/mia/diff-usion/results/ensemble/afhq",
308
- "class_names": ["class0", "class1"]
309
- },
310
- {
311
- "name": "lamp",
312
- "display_name": "Lamps",
313
- "description": "Dataset containing images of table lamps and floor lamps",
314
- "path": "compressed_datasets/lampsfar.zip",
315
- "direct_dataset_path": "/proj/vondrick2/mia/diff-usion/example_images/lamps",
316
- "checkpoint_path": "/proj/vondrick2/mia/diff-usion/lora_output_lampsfar/checkpoint-800",
317
- "embeddings_path": "/proj/vondrick2/mia/diff-usion/results/clip_image_embeds/lampsfar",
318
- "classifier_path": "/proj/vondrick2/mia/diff-usion/results/ensemble/lampsfar",
319
- "class_names": ["class0", "class1"]
320
- },
321
- {
322
- "name": "couches",
323
- "display_name": "Couches",
324
- "description": "Dataset containing images of chairs and floor",
325
- "path": "compressed_datasets/couches.zip",
326
- "direct_dataset_path": "/proj/vondrick2/mia/diff-usion/example_images/couches",
327
- "embeddings_path": "/proj/vondrick2/mia/diff-usion/results/clip_image_embeds/couches",
328
- "checkpoint_path": "/proj/vondrick2/mia/diff-usion/lora_output/couches/checkpoint-1000",
329
- "class_names": ["class0", "class1"]
330
- }
331
- ]
332
-
333
- # Function to get available example datasets
334
- def get_example_datasets():
335
- """Get list of available example datasets"""
336
- return [dataset["name"] for dataset in EXAMPLE_DATASETS]
337
-
338
- # Function to get example dataset info
339
- def get_example_dataset_info(name):
340
- """Get information about an example dataset"""
341
- for dataset in EXAMPLE_DATASETS:
342
- if dataset["name"] == name:
343
- return dataset
344
- return None
345
-
346
- #Function to check if we're using default parameters
347
- def is_using_default_params(dataset_name, custom_tskip, num_images_per_class):
348
- """Check if we're using default parameters for the given dataset"""
349
- if dataset_name is None:
350
- return False
351
- if "butterfly" in dataset_name.lower():
352
- return (custom_tskip == 70 or custom_tskip == "70") and num_images_per_class == 10
353
- elif "lamp" in dataset_name.lower():
354
- return (custom_tskip == 85 or custom_tskip == "85") and num_images_per_class == 10
355
- elif "couch" in dataset_name.lower():
356
- return (custom_tskip == 85 or custom_tskip == "85") and num_images_per_class == 10
357
- return False
358
-
359
- # # Function to get the output directory - either cache or regular output
360
- # def get_output_directory(dataset_name, is_default_params, cache_key):
361
- # """Get the appropriate output directory based on parameters"""
362
- # if is_default_params:
363
- # # Use cache directory
364
- # cache_path = CACHE_DIR / cache_key
365
- # cache_path.mkdir(exist_ok=True, parents=True)
366
-
367
- # # Create dataset-specific directory
368
- # dataset_dir = cache_path / dataset_name.replace(" ", "_").lower()
369
- # dataset_dir.mkdir(exist_ok=True, parents=True)
370
-
371
- # # Create class-specific directories
372
- # class0_to_class1_dir = dataset_dir / "class0_to_class1"
373
- # class1_to_class0_dir = dataset_dir / "class1_to_class0"
374
- # class0_to_class1_dir.mkdir(exist_ok=True, parents=True)
375
- # class1_to_class0_dir.mkdir(exist_ok=True, parents=True)
376
-
377
- # # Create context directory
378
- # context_dir = dataset_dir / "context"
379
- # context_dir.mkdir(exist_ok=True, parents=True)
380
-
381
- # return dataset_dir, class0_to_class1_dir, class1_to_class0_dir, context_dir
382
- # else:
383
- # # Use regular output directory
384
- # output_dir = Path(f"./results/{dataset_name.replace(' ', '_').lower()}")
385
- # output_dir.mkdir(exist_ok=True, parents=True)
386
-
387
- # # Create gifs directory with class-specific subdirectories
388
- # gifs_dir = output_dir / "gifs"
389
- # gifs_dir.mkdir(exist_ok=True, parents=True)
390
-
391
- # class0_to_class1_dir = gifs_dir / "class0_to_class1"
392
- # class1_to_class0_dir = gifs_dir / "class1_to_class0"
393
- # class0_to_class1_dir.mkdir(exist_ok=True, parents=True)
394
- # class1_to_class0_dir.mkdir(exist_ok=True, parents=True)
395
-
396
- # # Create context directory
397
- # context_dir = output_dir / "context"
398
- # context_dir.mkdir(exist_ok=True, parents=True)
399
-
400
- # return output_dir, class0_to_class1_dir, class1_to_class0_dir, context_dir
401
-
402
-
403
- def has_prediction_flipped(orig_preds, new_preds):
404
- """Check if any prediction has flipped from one class to another."""
405
- return ((orig_preds.preds > 0.5) != (new_preds.preds > 0.5)).any().item()
406
-
407
- # Function to extract uploaded zip or tar.gz file
408
- def extract_archive(archive_file, extract_dir):
409
- """Extract a zip or tar.gz file to the specified directory"""
410
- # Create a temporary directory for extraction
411
- temp_dir = Path(extract_dir)
412
- temp_dir.mkdir(parents=True, exist_ok=True)
413
-
414
- # Check file extension
415
- file_path = Path(archive_file)
416
-
417
- if file_path.suffix.lower() == '.zip':
418
- # Extract the zip file
419
- with zipfile.ZipFile(archive_file, 'r') as zip_ref:
420
- zip_ref.extractall(temp_dir)
421
- elif file_path.name.endswith('.tar.gz') or file_path.name.endswith('.tgz'):
422
- # Extract the tar.gz file
423
- with tarfile.open(archive_file, 'r:gz') as tar_ref:
424
- tar_ref.extractall(temp_dir)
425
- else:
426
- raise ValueError(f"Unsupported archive format: {file_path.suffix}. Please use .zip or .tar.gz")
427
-
428
- # Check if the extracted content has class0 and class1 folders
429
- # If not, try to find them in subdirectories
430
- class0_dir = temp_dir / "class0"
431
- class1_dir = temp_dir / "class1"
432
-
433
- if not (class0_dir.exists() and class1_dir.exists()):
434
- # Look for class0 and class1 in subdirectories
435
- for subdir in temp_dir.iterdir():
436
- if subdir.is_dir():
437
- if (subdir / "class0").exists() and (subdir / "class1").exists():
438
- # Move the class directories to the temp_dir
439
- shutil.move(str(subdir / "class0"), str(class0_dir))
440
- shutil.move(str(subdir / "class1"), str(class1_dir))
441
- break
442
-
443
- # Verify that we have the required directories
444
- if not (class0_dir.exists() and class1_dir.exists()):
445
- raise ValueError("The uploaded archive must contain 'class0' and 'class1' directories or a subdirectory containing them")
446
-
447
- return str(temp_dir)
448
-
449
- # Function to handle cached results (placeholder implementation)
450
- def get_cached_result_info(name):
451
- """Get information about a cached result (placeholder)"""
452
- # This is a placeholder - in a real implementation, you'd store and retrieve cached results
453
- return None
454
-
455
- # Modify the TwoClassDataset class to accept num_samples_per_class as a parameter
456
- class TwoClassDataset(Dataset):
457
- def __init__(self, root_dir, transform=None, num_samples_per_class=None):
458
- self.root_dir = Path(root_dir)
459
- #import pdb; pdb.set_trace()
460
- self.transform = transform
461
- if 'kermany' in str(self.root_dir):
462
- #import pdb; pdb.set_trace()
463
- self.class0_dir = self.root_dir / "NORMAL"
464
- self.class1_dir = self.root_dir / "DRUSEN"
465
- elif 'kiki_bouba' in str(self.root_dir):
466
- self.class0_dir = self.root_dir / "kiki"
467
- self.class1_dir = self.root_dir / "bouba"
468
- elif 'afhq' in str(self.root_dir):
469
- self.class0_dir = self.root_dir / "dog"
470
- self.class1_dir = self.root_dir / "cat"
471
- else:
472
- self.class0_dir = self.root_dir / "class0"
473
- self.class1_dir = self.root_dir / "class1"
474
-
475
- # Get image paths
476
- #import pdb; pdb.set_trace()
477
- self.class0_images = list(self.class0_dir.glob("*.*"))
478
- self.class1_images = list(self.class1_dir.glob("*.*"))
479
-
480
- # Limit the number of samples per class if specified
481
- if num_samples_per_class is not None:
482
- self.class0_images = self.class0_images[:num_samples_per_class]
483
- self.class1_images = self.class1_images[:num_samples_per_class]
484
-
485
- # Create image list and labels
486
- self.images = self.class0_images + self.class1_images
487
- self.labels = [0] * len(self.class0_images) + [1] * len(self.class1_images)
488
-
489
- def __len__(self):
490
- return len(self.images)
491
-
492
- def __getitem__(self, idx):
493
- img_path = self.images[idx]
494
- image = Image.open(img_path).convert("RGB")
495
- label = self.labels[idx]
496
-
497
- if self.transform:
498
- image = self.transform(image)
499
-
500
- return image, label, str(img_path)
501
-
502
- def compute_lpips_similarity(images1, images2, reduction=None):
503
- """Compute LPIPS similarity between two batches of images"""
504
- # This is a placeholder - in a real implementation, you'd use a proper LPIPS model
505
- # For demo purposes, we'll just return a random similarity score
506
- batch_size = images1.shape[0]
507
- similarity = torch.rand(batch_size, device=images1.device)
508
-
509
- if reduction == "mean":
510
- return similarity.mean()
511
- return similarity
512
-
513
- def get_direction_sign(idx: int):
514
- if idx == 0:
515
- sign = -1
516
- elif idx == 1:
517
- sign = 1
518
- else:
519
- raise ValueError("Currently two direction are supported in this script")
520
- return sign
521
-
522
- def add_text_to_image(image, text):
523
- """Add text to an image at the top with a nicer design"""
524
- draw = ImageDraw.Draw(image)
525
- # Use a default font
526
- try:
527
- font = ImageFont.truetype("arial.ttf", 24)
528
- except:
529
- font = ImageFont.load_default()
530
-
531
- # Add a semi-transparent gradient background for better readability
532
- text_width, text_height = draw.textsize(text, font=font) if hasattr(draw, 'textsize') else (200, 30)
533
-
534
- # Create gradient background
535
- for i in range(40):
536
- alpha = int(180 - i * 4) # Fade from 180 to 20 alpha
537
- if alpha < 0:
538
- alpha = 0
539
- draw.rectangle([(0, i), (image.width, i)], fill=(0, 0, 0, alpha))
540
-
541
- # Draw text at the top of the image
542
- draw.text((15, 10), text, fill="white", font=font)
543
- return image
544
-
545
- def create_gif(img1, img2, output_path):
546
- """Create a GIF that alternates between two images with elegant labels"""
547
- # Create copies of the images to avoid modifying the originals
548
- img1_copy = img1.copy()
549
- img2_copy = img2.copy()
550
-
551
- # Add labels to the images
552
- draw1 = ImageDraw.Draw(img1_copy)
553
- draw2 = ImageDraw.Draw(img2_copy)
554
-
555
- try:
556
- # Use a larger font size for better visibility
557
- font = ImageFont.truetype("arial.ttf", 36) # Increased from 28 to 36
558
- except:
559
- font = ImageFont.load_default()
560
-
561
- # Add a subtle shadow effect for better visibility
562
- padding = 15
563
-
564
- # Original image - add text with shadow effect
565
- # First draw shadow/outline
566
- for offset in [(1,1), (-1,1), (1,-1), (-1,-1)]:
567
- draw1.text(
568
- (padding + offset[0], padding + offset[1]),
569
- "Original",
570
- fill=(0, 0, 0, 180),
571
- font=font
572
- )
573
-
574
- # Then draw the main text
575
- draw1.text(
576
- (padding, padding),
577
- "Original",
578
- fill=(255, 255, 255, 230),
579
- font=font
580
- )
581
-
582
- # Generated image - add text with shadow effect
583
- # First draw shadow/outline
584
- for offset in [(1,1), (-1,1), (1,-1), (-1,-1)]:
585
- draw2.text(
586
- (padding + offset[0], padding + offset[1]),
587
- "Generated",
588
- fill=(0, 0, 0, 180),
589
- font=font
590
- )
591
-
592
- # Then draw the main text
593
- draw2.text(
594
- (padding, padding),
595
- "Generated",
596
- fill=(255, 255, 255, 230),
597
- font=font
598
- )
599
-
600
- # Increase duration to 1 second per image (1000ms)
601
- imageio.mimsave(output_path, [img1_copy, img2_copy], duration=1000, loop=0)
602
- return output_path
603
-
604
- def train_classifier(model, train_loader, val_loader, epochs, lr, device, patience=5):
605
- """Train a classifier model with early stopping"""
606
- global classifier_should_stop
607
-
608
- loss_fn = nn.BCEWithLogitsLoss()
609
- optimizer = torch.optim.Adam(model.parameters(), lr=lr)
610
- model.to(device)
611
-
612
- best_val_loss = float('inf')
613
- best_model_state = None
614
- early_stop_counter = 0
615
-
616
- for epoch in range(epochs):
617
- # Check if we should stop
618
- if classifier_should_stop:
619
- print("Classifier training cancelled")
620
- break
621
-
622
- # Training phase
623
- model.train()
624
- train_loss = 0.0
625
- train_correct = 0
626
-
627
- for images, labels, _ in tqdm(train_loader, desc=f"Epoch {epoch+1}/{epochs} - Training"):
628
- images = images.to(device)
629
- labels = labels.to(device).float().unsqueeze(1)
630
-
631
- optimizer.zero_grad()
632
- outputs = model(images)
633
- loss = loss_fn(outputs, labels)
634
- loss.backward()
635
- optimizer.step()
636
-
637
- train_loss += loss.item() * images.size(0)
638
- preds = (torch.sigmoid(outputs) > 0.5).float()
639
- train_correct += (preds == labels).sum().item()
640
-
641
- train_loss /= len(train_loader.dataset)
642
- train_acc = train_correct / len(train_loader.dataset)
643
-
644
- # Validation phase
645
- model.eval()
646
- val_loss = 0.0
647
- val_correct = 0
648
-
649
- with torch.no_grad():
650
- for images, labels, _ in tqdm(val_loader, desc=f"Epoch {epoch+1}/{epochs} - Validation"):
651
- images = images.to(device)
652
- labels = labels.to(device).float().unsqueeze(1)
653
-
654
- outputs = model(images)
655
- loss = loss_fn(outputs, labels)
656
-
657
- val_loss += loss.item() * images.size(0)
658
- preds = (torch.sigmoid(outputs) > 0.5).float()
659
- val_correct += (preds == labels).sum().item()
660
-
661
- val_loss /= len(val_loader.dataset)
662
- val_acc = val_correct / len(val_loader.dataset)
663
-
664
- print(f"Epoch {epoch+1}/{epochs} - "
665
- f"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, "
666
- f"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}")
667
-
668
- # Save best model and check for early stopping
669
- if val_loss < best_val_loss:
670
- best_val_loss = val_loss
671
- best_model_state = model.state_dict().copy()
672
- early_stop_counter = 0
673
- else:
674
- early_stop_counter += 1
675
- if early_stop_counter >= patience:
676
- print(f"Early stopping at epoch {epoch+1}")
677
- break
678
-
679
- # Load best model state
680
- if best_model_state is not None:
681
- model.load_state_dict(best_model_state)
682
-
683
- return model
684
-
685
- def train_ensemble_classifiers(train_dataset, val_dataset, classifiers_dir, epochs=10, lr=0.001):
686
- """Train an ensemble of classifiers"""
687
-
688
- classifiers_dir.mkdir(exist_ok=True, parents=True)
689
-
690
- # Limit training data if dataset is very large
691
- if len(train_dataset) > 1000:
692
- print(f"Limiting training data from {len(train_dataset)} to 1000 samples for faster training")
693
- indices = torch.randperm(len(train_dataset))[:1000]
694
- indices_val = torch.randperm(len(val_dataset))[:100]
695
- train_subset = torch.utils.data.Subset(train_dataset, indices)
696
- val_subset = torch.utils.data.Subset(val_dataset, indices_val)
697
- train_loader = DataLoader(train_subset, batch_size=BATCH_SIZE, shuffle=True)
698
- val_loader = DataLoader(val_subset, batch_size=BATCH_SIZE)
699
- else:
700
- train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
701
- val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE)
702
-
703
- # Define models for ensemble
704
- # MobileNetV2 (smallest and fastest model)
705
- mobilenet = models.mobilenet_v2(weights=MobileNet_V2_Weights.DEFAULT)
706
- mobilenet.classifier[1] = nn.Linear(mobilenet.last_channel, 1)
707
-
708
- # ResNet18
709
- resnet = models.resnet18(weights=ResNet18_Weights.DEFAULT)
710
- resnet.fc = nn.Linear(resnet.fc.in_features, 1)
711
-
712
- # EfficientNet-B0
713
- efficientnet = models.efficientnet_b0(weights=EfficientNet_B0_Weights.DEFAULT)
714
- efficientnet.classifier[1] = nn.Linear(efficientnet.classifier[1].in_features, 1)
715
-
716
- # Train each model
717
- ensemble_models = []
718
- model_names = ["efficientnet"] #"mobilenet", "resnet",
719
- models_list = [efficientnet] #mobilenet, resnet,
720
-
721
- for name, model in zip(model_names, models_list):
722
- print(f"Training {name}...")
723
- trained_model = train_classifier(
724
- model=model,
725
- train_loader=train_loader,
726
- val_loader=val_loader,
727
- epochs=epochs,
728
- lr=lr,
729
- device=DEVICE,
730
- patience=1 # Early stop after 1 epoch without improvement
731
- )
732
-
733
- # Save model
734
- save_path = classifiers_dir / f"{name}.pth"
735
- torch.save(trained_model, save_path)
736
- ensemble_models.append(trained_model)
737
-
738
- print(f"Saved {name} to {save_path}")
739
-
740
- return ensemble_models
741
-
742
- # Modify the update_progress_status function to be more informative
743
- def update_progress_status():
744
- """Update the progress status for the counterfactual generation"""
745
- global images_processed, total_images_to_process, is_processing
746
-
747
- if not is_processing:
748
- if images_processed > 0:
749
- return f"Processing complete. Generated {images_processed} counterfactual images."
750
- return "Ready to process images."
751
-
752
- if total_images_to_process == 0:
753
- return "Preparing to process images..."
754
-
755
- percentage = (images_processed / total_images_to_process) * 100
756
- return f"Progress: {images_processed}/{total_images_to_process} images processed ({percentage:.1f}%)"
757
-
758
- # Add function to cancel generation
759
- def cancel_generation():
760
- """Cancel all ongoing processes"""
761
- global generation_should_stop, classifier_should_stop, embedding_should_stop, lora_should_stop
762
-
763
- # Set all stop flags
764
- generation_should_stop = True
765
- classifier_should_stop = True
766
- embedding_should_stop = True
767
- lora_should_stop = True
768
-
769
- return "All processes have been requested to stop. This may take a moment to complete."
770
-
771
- def save_results_to_cache(output_dir, cache_key):
772
- """Save generated results to cache directory"""
773
- cache_path = CACHE_DIR / cache_key
774
- cache_path.mkdir(exist_ok=True, parents=True)
775
-
776
- # Copy gifs directory
777
- output_gifs_dir = Path(output_dir) / "gifs"
778
- cache_gifs_dir = cache_path / "gifs"
779
-
780
- if output_gifs_dir.exists():
781
- # Remove existing cache if it exists
782
- if cache_gifs_dir.exists():
783
- shutil.rmtree(cache_gifs_dir)
784
-
785
- # Copy the new results, maintaining subdirectory structure
786
- shutil.copytree(output_gifs_dir, cache_gifs_dir)
787
-
788
- # Copy context images if they exist
789
- output_context_dir = Path(output_dir) / "context"
790
- cache_context_dir = cache_path / "context"
791
-
792
- if output_context_dir.exists():
793
- if cache_context_dir.exists():
794
- shutil.rmtree(cache_context_dir)
795
- shutil.copytree(output_context_dir, cache_context_dir)
796
-
797
- # Update the process_with_selected_dataset function to handle the new directory structure
798
- def process_with_selected_dataset(zip_file, output_dir, dataset_display_name, checkpoint_path=None, train_clf=True,
799
- is_direct_path=False, direct_path=None, embeddings_path=None,
800
- classifier_path=None, use_classifier_stopping=True, custom_tskip=85,
801
- manip_val=2):
802
-
803
- print(f"\nProcessing with dataset: {dataset_display_name}")
804
-
805
- # Find the selected dataset
806
- selected_dataset = None
807
- for dataset in EXAMPLE_DATASETS:
808
- if dataset["display_name"] == dataset_display_name:
809
- selected_dataset = dataset
810
- break
811
-
812
- if not selected_dataset:
813
- print("Error: No dataset selected")
814
- return "No dataset selected", [], [], [], "Error: No dataset selected", None, None
815
-
816
- # Generate cache key
817
- cache_key = get_cache_key(
818
- selected_dataset["name"], checkpoint_path, False, embeddings_path,
819
- classifier_path, use_classifier_stopping, custom_tskip,
820
- manip_val,
821
- )
822
-
823
- print(f"Generated cache key: {cache_key}")
824
-
825
- # Check if cache exists
826
- cache_path = CACHE_DIR / cache_key
827
- dataset_dir = cache_path / "gifs"
828
- print(f"Looking for cache in: {cache_path}")
829
- print(f"Looking for gifs in: {dataset_dir}")
830
- print(f"Cache exists: {cache_path.exists()}")
831
- print(f"Gifs dir exists: {dataset_dir.exists()}")
832
-
833
- #import pdb; pdb.set_trace()
834
- if cache_path.exists() and dataset_dir.exists():
835
- current_cache_key = cache_key
836
- print(f"Found cached results for key: {cache_key}")
837
-
838
- # Get paths to class-specific directories
839
- class0_to_class1_dir = dataset_dir / "class0_to_class1"
840
- class1_to_class0_dir = dataset_dir / "class1_to_class0"
841
- context_dir = cache_path/ "context"
842
-
843
- # Get all GIF paths
844
- class0_to_class1_gifs = list(class0_to_class1_dir.glob("*.gif")) if class0_to_class1_dir.exists() else []
845
- class1_to_class0_gifs = list(class1_to_class0_dir.glob("*.gif")) if class1_to_class0_dir.exists() else []
846
-
847
- # Sort the GIFs by filename for consistent ordering
848
- class0_to_class1_gifs.sort(key=lambda p: p.name)
849
- class1_to_class0_gifs.sort(key=lambda p: p.name)
850
-
851
- # Get context images
852
- class0_context = context_dir / "class0_sample.jpg" if (context_dir / "class0_sample.jpg").exists() else None
853
- class1_context = context_dir / "class1_sample.jpg" if (context_dir / "class1_sample.jpg").exists() else None
854
-
855
- # Convert paths to strings
856
- class0_to_class1_paths = [str(p) for p in class0_to_class1_gifs]
857
- class1_to_class0_paths = [str(p) for p in class1_to_class0_gifs]
858
- all_gifs = class0_to_class1_paths + class1_to_class0_paths
859
-
860
- # Update the global gallery variables
861
- global displayed_results, displayed_results_class0_to_class1, displayed_results_class1_to_class0
862
- displayed_results = all_gifs
863
- displayed_results_class0_to_class1 = class0_to_class1_paths
864
- displayed_results_class1_to_class0 = class1_to_class0_paths
865
-
866
- status_message = f"Using cached results with t-skip={custom_tskip}, manip_scale={manip_val}"
867
-
868
- # Return cached results
869
- return (
870
- "Using cached results for default parameters.",
871
- displayed_results,
872
- displayed_results_class0_to_class1,
873
- displayed_results_class1_to_class0,
874
- status_message,
875
- str(class0_context) if class0_context else None,
876
- str(class1_context) if class1_context else None
877
- )
878
-
879
- else:
880
- print("No cached results found, processing dataset...")
881
- return "No cached results found, processing dataset...", [], [], [], "No cached results found, processing dataset...", None, None
882
- return
883
-
884
-
885
-
886
- # def process_and_clear(example_datasets_dropdown, checkpoint_path_state,
887
- # is_direct_path_state, direct_path_state, embeddings_path_state,
888
- # classifier_path_state, use_classifier_stopping, custom_tskip,
889
- # manip_val):
890
- # """Clear folders first, then process the dataset"""
891
- # # Clear folders first
892
- # clear_output_folders()
893
-
894
- # # Then process the dataset
895
- # return process_with_selected_dataset(
896
- # None, # input_zip (always None)
897
- # "./output", # output_dir (hardcoded)
898
- # example_datasets_dropdown,
899
- # checkpoint_path_state,
900
- # False, # train_clf (always False)
901
- # is_direct_path_state,
902
- # direct_path_state,
903
- # embeddings_path_state,
904
- # classifier_path_state,
905
- # use_classifier_stopping,
906
- # custom_tskip,
907
- # manip_val
908
- # )
909
-
910
- def process_and_clear(example_datasets_dropdown, checkpoint_path_state,
911
- is_direct_path_state, direct_path_state, embeddings_path_state,
912
- classifier_path_state, use_classifier_stopping, custom_tskip,
913
- manip_val):
914
- """Clear galleries first, then process the dataset"""
915
- # Clear galleries but keep example images
916
- clear_output_folders()
917
-
918
- # Process the dataset
919
- result = process_with_selected_dataset(
920
- None, # input_zip (always None)
921
- "./output", # output_dir (hardcoded)
922
- example_datasets_dropdown,
923
- checkpoint_path_state,
924
- False, # train_clf (always False)
925
- is_direct_path_state,
926
- direct_path_state,
927
- embeddings_path_state,
928
- classifier_path_state,
929
- use_classifier_stopping,
930
- custom_tskip,
931
- manip_val
932
- )
933
-
934
- # Return all outputs except example images
935
- return (
936
- result[1], # gallery
937
- result[2], # gallery_class0_to_class1
938
- result[3], # gallery_class1_to_class0
939
- result[4], # progress_status # Don't update class1_context_image
940
- )
941
-
942
- def update_example_images(dataset_display_name):
943
- """Update the example images based on the selected dataset"""
944
- print(f"\nUpdating example images for {dataset_display_name}")
945
-
946
- # Find the dataset info
947
- selected_dataset = None
948
- for dataset in EXAMPLE_DATASETS:
949
- print(f"Checking dataset: {dataset['display_name']}", dataset_display_name)
950
- if dataset["display_name"] == dataset_display_name:
951
- selected_dataset = dataset
952
- print(f"Selected dataset: {selected_dataset}")
953
- break
954
-
955
- class_names = selected_dataset.get("class_names", None)
956
-
957
- if selected_dataset:
958
- dataset_dir = selected_dataset.get("direct_dataset_path")
959
- print(f"Dataset directory: {dataset_dir}")
960
-
961
- if dataset_dir:
962
- # Debug: List all files in the directory
963
- print("Contents of directory:")
964
- for path in Path(dataset_dir).rglob("*"):
965
- print(f" {path}")
966
-
967
- # Try to find class0 and class1 images
968
-
969
- class0_path = Path(dataset_dir) / class_names[0]
970
- class1_path = Path(dataset_dir) / class_names[1]
971
- print(f"Looking in class0: {class0_path}")
972
- print(f"Looking in class1: {class1_path}")
973
-
974
- class0_img = next((str(p) for p in Path(dataset_dir).glob(f"{class_names[0]}/*.*")), None)
975
- class1_img = next((str(p) for p in Path(dataset_dir).glob(f"{class_names[1]}/*.*")), None)
976
-
977
- print(f"Found images:\nclass0={class0_img}\nclass1={class1_img}")
978
- return class0_img, class1_img
979
-
980
- print("No images found")
981
- return None, None
982
- # Add a state variable to store the direct dataset path
983
- direct_path_state = gr.State(None)
984
- # Map display names back to internal names (add this back)
985
- def get_name_from_display(display_name):
986
- for dataset in EXAMPLE_DATASETS:
987
- if dataset["display_name"] == display_name:
988
- return dataset["name"]
989
- return None
990
-
991
- # Modify the use_selected_dataset function
992
- def use_selected_dataset(display_name):
993
- name = get_name_from_display(display_name)
994
- if not name:
995
- print("No dataset name found")
996
- return None, None, False, None, None, None
997
-
998
- dataset_info = get_example_dataset_info(name)
999
-
1000
- # Check if there's a direct dataset path available
1001
- if dataset_info and "direct_dataset_path" in dataset_info and os.path.exists(dataset_info["direct_dataset_path"]):
1002
- print(f"Using direct dataset path: {dataset_info['direct_dataset_path']}")
1003
- # Return paths for direct dataset, checkpoint, embeddings, and classifiers
1004
- return None, dataset_info["checkpoint_path"], True, dataset_info["direct_dataset_path"], \
1005
- dataset_info.get("embeddings_path"), dataset_info.get("classifier_path")
1006
- elif dataset_info and os.path.exists(dataset_info["path"]):
1007
- # Return the archive path and other paths
1008
- return dataset_info["path"], dataset_info["checkpoint_path"], False, None, \
1009
- dataset_info.get("embeddings_path"), dataset_info.get("classifier_path")
1010
- return None, None, False, None, None, None
1011
- def reset_galleries():
1012
- """Reset all galleries when changing datasets or parameters"""
1013
- global displayed_results, displayed_results_class0_to_class1, displayed_results_class1_to_class0
1014
- global current_cache_key # Also reset the cache key
1015
-
1016
- displayed_results = []
1017
- displayed_results_class0_to_class1 = []
1018
- displayed_results_class1_to_class0 = []
1019
- current_cache_key = None # Reset the cache key
1020
-
1021
- # Clear the result queue if it exists
1022
- while not result_queue.empty():
1023
- result_queue.get()
1024
-
1025
- return [], [], [], "Galleries reset"
1026
- def clear_output_folders():
1027
- """Delete the output/gifs and output/context folders and their contents"""
1028
- import shutil
1029
- from pathlib import Path
1030
-
1031
- # Folders to clear
1032
- folders = ["gifs", "context"]
1033
-
1034
- for folder in folders:
1035
- folder_path = Path("./output") / folder
1036
- if folder_path.exists():
1037
- shutil.rmtree(folder_path)
1038
- print(f"Deleted {folder_path}")
1039
-
1040
- def create_gradio_interface():
1041
- # Create temporary directories for uploads
1042
- temp_dir = Path("./temp_uploads")
1043
- temp_dir.mkdir(exist_ok=True, parents=True)
1044
-
1045
- clear_output_folders()
1046
-
1047
-
1048
-
1049
- lora_temp_dir = Path("./temp_lora_uploads")
1050
- lora_temp_dir.mkdir(exist_ok=True, parents=True)
1051
-
1052
- # Get initial list of example datasets
1053
- example_datasets = get_example_datasets()
1054
-
1055
- with gr.Blocks(css=css) as demo:
1056
- # Add the header at the top level to span across all columns
1057
- with gr.Row(elem_classes="full-width-header"):
1058
- with gr.Column():
1059
- gr.HTML("""
1060
- <div class="header">
1061
- <h1>DIFFusion Demo</h1>
1062
- <p class="subtitle">Generate fine-grained edits to images using another class of images as guidance.</p>
1063
- <p class="contact-info">For any questions/comments/issues with this demo, please email mia.chiquier@cs.columbia.edu.🤖</p>
1064
- </div>
1065
- """)
1066
-
1067
- # Main content row with sidebar, config column and results column
1068
- with gr.Row(elem_classes="content-row"):
1069
- # Sidebar for example datasets
1070
- with gr.Column(scale=1, elem_classes="sidebar"):
1071
- gr.HTML('<div class="section-header">Example Datasets</div>')
1072
-
1073
- # Create a dropdown for example datasets
1074
- example_datasets_dropdown = gr.Dropdown(
1075
- choices=[dataset["display_name"] for dataset in EXAMPLE_DATASETS],
1076
- value=next((dataset["display_name"] for dataset in EXAMPLE_DATASETS if "lamp" in dataset["display_name"].lower()), None), # Set lamp as default
1077
- label="Example Datasets",
1078
- info="Select a pre-loaded dataset to use"
1079
- )
1080
-
1081
- # Add dataset descriptions directly in the dropdown info
1082
- dataset_descriptions = {dataset["display_name"]: dataset.get("description", "") for dataset in EXAMPLE_DATASETS}
1083
-
1084
- # Add some spacing
1085
- gr.HTML("<div style='height: 20px;'></div>")
1086
-
1087
- # Add a hidden state for the dataset description (we'll still update it but not display it)
1088
- dataset_description = gr.Textbox(visible=False)
1089
-
1090
- # Main content area
1091
- with gr.Column(scale=2, elem_classes="main-container"):
1092
- # Paper info and configuration
1093
- with gr.Column():
1094
- with gr.Column(elem_classes="paper-info"):
1095
- gr.HTML("""
1096
- <h3>DIFFusion Marketing Demo</h3>
1097
- <p>Ever struggled to describe a visual transformation in AI image editing? Same. It's often difficult to identify the exact transformation you want to perform. This insight led us to develop an image-guided method that enables precise edits by learning from class differences, rather than requiring predefined descriptions. Our method automatically identifies and applies the transformation, making it especially useful in science where researchers can visualize subtle differences between similar-looking image categories. While science was our focus, I realized the method is also ideal for marketing. Product differences can be difficult to articulate verbally, and we don't always know how to best adapt aesthetic elements to showcase new products effectively. This demo was created for marketers to explore these capabilities with their own product imagery. Of course, if you have a science dataset, this demo works great for that too.</p>
1098
- """)
1099
-
1100
- with gr.Column(elem_classes="paper-info"):
1101
- gr.HTML("""
1102
- <h3>Optimal Usage Conditions & Limitations</h3>
1103
- <p>Our method works best when the images per class are centered and aligned. However, if you have a large set of images per class (>50), this becomes less important. We do not support the use of our method on images of humans, as it is underexplored.</p>
1104
- """)
1105
-
1106
- # Counterfactual Generation Section
1107
- gr.HTML('<div class="section-header">Counterfactual Generation</div>')
1108
-
1109
- # with gr.Column(elem_classes="upload-info"):
1110
- # gr.HTML("""
1111
- # <p><strong>Dataset Format:</strong> Upload a zip file containing two folders named 'class0' and 'class1',
1112
- # each containing images of the respective class.</p>
1113
- # """)
1114
-
1115
- # with gr.Row():
1116
- # input_zip = gr.File(
1117
- # label="Upload Custom Dataset (ZIP or TAR.GZ file)",
1118
- # file_types=[".zip", ".tar.gz", ".tgz"],
1119
- # type="filepath"
1120
- # )
1121
- # # Hide the output directory by using elem_classes
1122
- # output_dir = gr.Textbox(
1123
- # label="Output Directory",
1124
- # value="./output",
1125
- # elem_classes="hidden-element"
1126
- # )
1127
-
1128
- # with gr.Row():
1129
- # gr.HTML('<div class="section-header">LoRA Training</div>')
1130
-
1131
- # with gr.Column(elem_classes="upload-info"):
1132
- # gr.HTML("""
1133
- # <p><strong>Dataset Format:</strong> Upload a zip file containing two folders named 'class0' and 'class1',
1134
- # each containing images of the respective class for training the LoRA model.</p>
1135
- # """)
1136
-
1137
- # with gr.Row():
1138
- # lora_output_dir = gr.Textbox(
1139
- # label="LoRA Output Directory",
1140
- # value="./lora_output"
1141
- # )
1142
-
1143
- # gr.HTML("""
1144
- # <div class="parameter-box">
1145
- # <p>Default LoRA Training Parameters:</p>
1146
- # <ul>
1147
- # <li>Epochs: 5</li>
1148
- # <li>Learning Rate: 1e-4</li>
1149
- # <li>Batch Size: 32</li>
1150
- # <li>LoRA Rank: 4</li>
1151
- # <li>LoRA Alpha: 32</li>
1152
- # <li>Max Training Steps: 1000</li>
1153
- # </ul>
1154
- # </div>
1155
- # """)
1156
-
1157
- # train_lora_btn = gr.Button("Train LoRA Model", elem_classes="btn-primary")
1158
- # lora_status_box = gr.Textbox(label="LoRA Training Status", value="Ready to train LoRA model")
1159
- # train_clf = gr.Checkbox(label="Train New Classifiers", value=False)
1160
-
1161
- with gr.Row():
1162
- use_classifier_stopping = gr.State(False)#
1163
-
1164
- custom_tskip = gr.Dropdown(
1165
- choices=[55, 60, 65, 70, 75, 80, 85, 90, 95, 100],
1166
- value=85, # default value
1167
- label="Custom T-Skip Value",
1168
- info="Select a t-skip value",
1169
- visible=True
1170
- )
1171
-
1172
- # Add a text box for number of images per class
1173
- with gr.Row():
1174
- manip_val = gr.Dropdown(
1175
- choices=[1.0, 1.5, 2.0],
1176
- value=2.0, # default value
1177
- label="Manip scale",
1178
- info="Select a manip scale",
1179
- visible=True
1180
- )
1181
- #
1182
-
1183
- with gr.Row():
1184
- process_btn = gr.Button("Generate Counterfactuals", elem_classes="btn-primary")
1185
- cancel_btn = gr.Button("Cancel Generation", elem_classes="btn-primary")
1186
-
1187
- # Status for the main column
1188
- #status = gr.Textbox(label="Status", value="Ready to generate counterfactuals")
1189
-
1190
- # Results column
1191
- with gr.Column(scale=2, elem_classes="results-container"):
1192
- # Class Examples section header - MOVED HERE
1193
- gr.HTML('<div class="section-header">Class Examples</div>')
1194
-
1195
- # Class example images - MOVED HERE
1196
- with gr.Row():
1197
- class0_context_image = gr.Image(label="Class 0 Example", type="filepath", height=256)
1198
- class1_context_image = gr.Image(label="Class 1 Example", type="filepath", height=256)
1199
-
1200
- # Results section header
1201
- gr.HTML('<div class="section-header">Results</div>')
1202
-
1203
-
1204
-
1205
- default_dataset = next((dataset["display_name"] for dataset in EXAMPLE_DATASETS if "lamps" in dataset["display_name"].lower()), None)
1206
- if default_dataset:
1207
- # Initial load of example images
1208
- class0_img, class1_img = update_example_images(default_dataset)
1209
- if class0_img and class1_img:
1210
- class0_context_image.value = class0_img # Directly set the value
1211
- class1_context_image.value = class1_img
1212
-
1213
- print(f"Class 0 image: {class0_context_image.value}")
1214
- print(f"Class 1 image: {class1_context_image.value}")
1215
-
1216
- # Add tabs for different direction signs - make "All Results" the default tab
1217
- with gr.Tabs(elem_classes="tabs-container") as result_tabs:
1218
- with gr.TabItem("All Results"):
1219
- gallery = gr.Gallery(
1220
- label="Generated Images",
1221
- show_label=False,
1222
- elem_id="gallery_all",
1223
- columns=4, # Show 4 images per row
1224
- rows=None, # Let it adjust rows automatically
1225
- height="auto",
1226
- allow_preview=True,
1227
- preview=False,
1228
- object_fit="contain"
1229
- )
1230
-
1231
- with gr.TabItem("Class 0 → Class 1"):
1232
- gallery_class0_to_class1 = gr.Gallery(
1233
- label="Class 0 to Class 1",
1234
- show_label=False,
1235
- elem_id="gallery_0to1",
1236
- columns=4, # Show 4 images per row
1237
- rows=None, # Let it adjust rows automatically
1238
- height="auto",
1239
- allow_preview=True,
1240
- preview=True,
1241
- object_fit="contain"
1242
- )
1243
-
1244
- with gr.TabItem("Class 1 → Class 0"):
1245
- gallery_class1_to_class0 = gr.Gallery(
1246
- label="Class 1 to Class 0",
1247
- show_label=False,
1248
- elem_id="gallery_1to0",
1249
- columns=4, # Show 4 images per row
1250
- rows=None, # Let it adjust rows automatically
1251
- height="auto",
1252
- allow_preview=True,
1253
- preview=True,
1254
- object_fit="contain"
1255
- )
1256
- # with gr.TabItem("All Results"):
1257
- # gallery = gr.Gallery(
1258
- # columns=[3],
1259
- # rows=[3],
1260
- # height="auto",
1261
- # allow_preview=True, # Make sure this is enabled
1262
- # preview=True, # Try setting this explicitly
1263
- # object_fit="contain" # Try different fit modes
1264
- # )
1265
-
1266
- # with gr.TabItem("Class 0 → Class 1"):
1267
- # gallery_class0_to_class1 = gr.Gallery(
1268
- # columns=[3],
1269
- # rows=[3],
1270
- # height="auto",
1271
- # allow_preview=True, # Make sure this is enabled
1272
- # preview=True, # Try setting this explicitly
1273
- # object_fit="contain" # Try different fit modes
1274
- # )
1275
-
1276
- # with gr.TabItem("Class 1 → Class 0"):
1277
- # gallery_class1_to_class0 = gr.Gallery(
1278
- # columns=[3],
1279
- # rows=[3],
1280
- # height="auto",
1281
- # allow_preview=True, # Make sure this is enabled
1282
- # preview=True, # Try setting this explicitly
1283
- # object_fit="contain" # Try different fit modes
1284
- # )
1285
-
1286
- # Add a progress status box in the results column
1287
- progress_status = gr.Textbox(
1288
- label="Progress",
1289
- value="Ready to process",
1290
- interactive=False
1291
- )
1292
-
1293
- # Define state variables inside the function
1294
- #set the default to these to be those for the lamp dataset
1295
- default_dataset = next((dataset for dataset in EXAMPLE_DATASETS if "lamp" in dataset["display_name"].lower()), None)
1296
- if default_dataset:
1297
- checkpoint_path_state = gr.State(default_dataset["checkpoint_path"])
1298
- is_direct_path_state = gr.State(False)
1299
- direct_path_state = gr.State(None)
1300
- embeddings_path_state = gr.State(default_dataset["embeddings_path"])
1301
- classifier_path_state = gr.State(default_dataset["classifier_path"])
1302
-
1303
- process_btn.click(
1304
- fn=process_and_clear,
1305
- inputs=[
1306
- example_datasets_dropdown, checkpoint_path_state,
1307
- is_direct_path_state, direct_path_state, embeddings_path_state,
1308
- classifier_path_state, use_classifier_stopping, custom_tskip,
1309
- manip_val
1310
- ],
1311
- outputs=[
1312
- gallery, # Make sure these variables are all defined
1313
- gallery_class0_to_class1, # and not None
1314
- gallery_class1_to_class0,
1315
- progress_status
1316
- ] # Removed 'status' since it wasn't defined
1317
- )
1318
-
1319
- # Set up the cancel button click handler
1320
- cancel_btn.click(
1321
- fn=cancel_generation,
1322
- inputs=None,
1323
- outputs=None
1324
- )
1325
-
1326
- num_images_per_class = gr.State(10)
1327
-
1328
- example_datasets_dropdown.change(
1329
- fn=reset_galleries, # Reset galleries but not example images
1330
- inputs=None,
1331
- outputs=[gallery, gallery_class0_to_class1, gallery_class1_to_class0, progress_status]
1332
- ).then( # Update dataset info
1333
- fn=update_dataset_info,
1334
- inputs=example_datasets_dropdown,
1335
- outputs=[dataset_description, checkpoint_path_state, is_direct_path_state, direct_path_state,
1336
- embeddings_path_state, classifier_path_state, custom_tskip]
1337
- ).then( # Set custom t-skip
1338
- fn=set_custom_tskip_for_dataset,
1339
- inputs=example_datasets_dropdown,
1340
- outputs=custom_tskip
1341
- ).then( # Change cache key
1342
- fn=change_cache_key,
1343
- inputs=[example_datasets_dropdown, num_images_per_class, use_classifier_stopping, custom_tskip],
1344
- outputs=None
1345
- ).then( # Update example images
1346
- fn=update_example_images,
1347
- inputs=example_datasets_dropdown,
1348
- outputs=[class0_context_image, class1_context_image]
1349
- ).then( # Automatically generate counterfactuals when dataset changes
1350
- fn=process_and_clear,
1351
- inputs=[
1352
- example_datasets_dropdown, checkpoint_path_state,
1353
- is_direct_path_state, direct_path_state, embeddings_path_state,
1354
- classifier_path_state, use_classifier_stopping, custom_tskip,
1355
- manip_val
1356
- ],
1357
- outputs=[gallery, gallery_class0_to_class1, gallery_class1_to_class0,
1358
- progress_status]
1359
- )
1360
-
1361
- # Load initial example images and generate counterfactuals for default dataset (Lamps)
1362
- demo.load(
1363
- fn=update_example_images,
1364
- inputs=example_datasets_dropdown,
1365
- outputs=[class0_context_image, class1_context_image]
1366
- ).then( # Initial counterfactual generation
1367
- fn=process_and_clear,
1368
- inputs=[
1369
- example_datasets_dropdown, checkpoint_path_state,
1370
- is_direct_path_state, direct_path_state, embeddings_path_state,
1371
- classifier_path_state, use_classifier_stopping, custom_tskip,
1372
- manip_val
1373
- ],
1374
- outputs=[gallery, gallery_class0_to_class1, gallery_class1_to_class0,
1375
- progress_status]
1376
- )
1377
-
1378
- # example_datasets_dropdown.change(
1379
- # fn=reset_galleries, # Reset first
1380
- # inputs=None,
1381
- # outputs=[gallery, gallery_class0_to_class1, gallery_class1_to_class0, progress_status]
1382
- # ).then( # Update dataset info
1383
- # fn=update_dataset_info,
1384
- # inputs=example_datasets_dropdown,
1385
- # outputs=[dataset_description, checkpoint_path_state, is_direct_path_state, direct_path_state,
1386
- # embeddings_path_state, classifier_path_state, custom_tskip_state]
1387
- # ).then( # Set custom t-skip
1388
- # fn=set_custom_tskip_for_dataset,
1389
- # inputs=example_datasets_dropdown,
1390
- # outputs=custom_tskip
1391
- # ).then( # Change cache key
1392
- # fn=change_cache_key,
1393
- # inputs=[example_datasets_dropdown, manip_val, use_classifier_stopping, custom_tskip],
1394
- # outputs=None
1395
- # ).then( # Update example images
1396
- # fn=lambda display_name: update_example_images(display_name),
1397
- # inputs=example_datasets_dropdown,
1398
- # outputs=[class0_context_image, class1_context_image]
1399
- # )
1400
-
1401
-
1402
- # process_btn.click(
1403
- # fn=process_and_clear,
1404
- # inputs=[
1405
- # example_datasets_dropdown, checkpoint_path_state,
1406
- # is_direct_path_state, direct_path_state, embeddings_path_state,
1407
- # classifier_path_state, use_classifier_stopping, custom_tskip,
1408
- # manip_val
1409
- # ],
1410
- # outputs=[status, gallery, gallery_class0_to_class1, gallery_class1_to_class0,
1411
- # progress_status, class0_context_image, class1_context_image]
1412
- # )
1413
-
1414
-
1415
- # # Set up the click event for LoRA training
1416
- # train_lora_btn.click(
1417
- # fn=start_lora_training,
1418
- # inputs=[input_zip, lora_output_dir],
1419
- # outputs=[lora_status_box]
1420
- # )
1421
-
1422
- # # Set up periodic status checking for LoRA training
1423
- # demo.load(
1424
- # fn=check_lora_status,
1425
- # inputs=None,
1426
- # outputs=lora_status_box,
1427
- # every=5 # Check every 5 seconds
1428
- # )
1429
-
1430
- # Add a periodic refresh for the galleries
1431
- # Add a periodic refresh for the galleries
1432
-
1433
-
1434
- # Add this event handler:
1435
- # example_datasets_dropdown.change(
1436
- # fn=reset_galleries,
1437
- # inputs=None,
1438
- # outputs=[gallery, gallery_class0_to_class1, gallery_class1_to_class0, progress_status]
1439
- # )
1440
-
1441
- return demo
1442
-
1443
-
1444
-
1445
- def update_dataset_info(dataset_display_name):
1446
- """Update dataset description and paths when dropdown changes"""
1447
- # Find the selected dataset
1448
- selected_dataset = None
1449
- for dataset in EXAMPLE_DATASETS:
1450
- if dataset["display_name"] == dataset_display_name:
1451
- selected_dataset = dataset
1452
- break
1453
-
1454
- if not selected_dataset:
1455
- return "No dataset selected", None, False, None, None, None, None
1456
-
1457
- # Get dataset description
1458
- description = selected_dataset.get("description", "No description available")
1459
-
1460
- # Get paths
1461
- checkpoint_path = selected_dataset.get("checkpoint_path", None)
1462
- direct_path = selected_dataset.get("direct_dataset_path", None)
1463
- is_direct_path = direct_path is not None
1464
- embeddings_path = selected_dataset.get("embeddings_path", None)
1465
- classifier_path = selected_dataset.get("classifier_path", None)
1466
-
1467
- # Set default custom_tskip based on dataset
1468
- custom_tskip = None
1469
- if "butterfly" in dataset_display_name.lower():
1470
- custom_tskip = 70 # Set to 70 for butterfly
1471
- elif "lamp" in dataset_display_name.lower():
1472
- custom_tskip = 85 # Set to 85 for lamp
1473
-
1474
- print(f"Setting custom_tskip to {custom_tskip} for dataset {dataset_display_name}")
1475
-
1476
- return description, checkpoint_path, is_direct_path, direct_path, embeddings_path, classifier_path, custom_tskip
1477
-
1478
-
1479
-
1480
- # Function to generate a cache key based on parameters
1481
- def get_cache_key(dataset_name, checkpoint_path, train_clf, embeddings_path,
1482
- classifier_path, use_classifier_stopping, custom_tskip, manip_val):
1483
- """Generate a unique cache key based on the processing parameters"""
1484
- # Create a dictionary of parameters
1485
- params = {
1486
- "dataset_name": dataset_name,
1487
- "checkpoint_path": str(checkpoint_path),
1488
- "train_clf": train_clf,
1489
- "embeddings_path": str(embeddings_path),
1490
- "classifier_path": str(classifier_path),
1491
- "use_classifier_stopping": use_classifier_stopping,
1492
- "custom_tskip": custom_tskip,
1493
- "manip_val": float(manip_val)
1494
- }
1495
- print(f"Params: {params}")
1496
-
1497
- # Convert to JSON string and hash
1498
- params_str = json.dumps(params, sort_keys=True)
1499
- return hashlib.md5(params_str.encode()).hexdigest()
1500
- def change_cache_key(dataset_name, manip_val, use_classifier_stopping, custom_tskip):
1501
- """Change the cache key based on the selected dataset"""
1502
- global current_cache_key
1503
-
1504
- # Find the selected dataset from EXAMPLE_DATASETS
1505
- selected_dataset = None
1506
- for dataset in EXAMPLE_DATASETS:
1507
- if dataset["display_name"] == dataset_name:
1508
- selected_dataset = dataset
1509
- break
1510
-
1511
- if not selected_dataset:
1512
- print(f"No dataset found for name: {dataset_name}")
1513
- return
1514
-
1515
- # Get all parameters from the selected dataset
1516
- checkpoint_path = selected_dataset.get("checkpoint_path", None)
1517
- embeddings_path = selected_dataset.get("embeddings_path", None)
1518
- classifier_path = selected_dataset.get("classifier_path", None)
1519
-
1520
-
1521
- # Generate and set the cache key
1522
- current_cache_key = get_cache_key(
1523
- selected_dataset["name"], # Use internal name instead of display name
1524
- checkpoint_path,
1525
- False, # train_clf is always False
1526
- embeddings_path,
1527
- classifier_path,
1528
- use_classifier_stopping,
1529
- custom_tskip,
1530
- manip_val
1531
- )
1532
-
1533
- # Function to check if cached results exist
1534
- def check_cache(cache_key):
1535
- """Check if cached results exist for the given key"""
1536
- cache_path = CACHE_DIR / cache_key
1537
- return cache_path.exists() and (cache_path / "gifs").exists()
1538
-
1539
- # Add this function to create context images for each class
1540
- def create_context_image(image_paths, output_path, title, preferred_index=0):
1541
- """Create a context image showing samples from a class
1542
-
1543
- Args:
1544
- image_paths: List of paths to images in the class
1545
- output_path: Where to save the context image
1546
- title: Title for the image
1547
- preferred_index: Index of the preferred image to use (default: 0)
1548
- """
1549
- if not image_paths:
1550
- # Create a blank image if no samples are available
1551
- img = Image.new('RGB', (512, 512), color=(240, 240, 240))
1552
- draw = ImageDraw.Draw(img)
1553
- try:
1554
- font = ImageFont.truetype("arial.ttf", 32)
1555
- except:
1556
- font = ImageFont.load_default()
1557
- draw.text((256, 256), "No samples available", fill=(80, 80, 80), font=font, anchor="mm")
1558
- img.save(output_path)
1559
- return
1560
-
1561
- # Use the preferred index if available, otherwise use the first image
1562
- img_index = min(preferred_index, len(image_paths) - 1)
1563
- img = Image.open(image_paths[img_index]).convert("RGB")
1564
- img = img.resize((512, 512), Image.LANCZOS)
1565
-
1566
- # Add title
1567
- draw = ImageDraw.Draw(img)
1568
- try:
1569
- font = ImageFont.truetype("arial.ttf", 32)
1570
- except:
1571
- font = ImageFont.load_default()
1572
-
1573
- # Draw a semi-transparent background for the title
1574
- draw.rectangle([(0, 0), (img.width, 50)], fill=(0, 0, 0, 180))
1575
-
1576
- # Save the context image
1577
- img.save(output_path)
1578
-
1579
- # Fix the update_custom_tskip function
1580
- def update_custom_tskip(tskip_value):
1581
- """Update the custom_tskip input field with the value from the state"""
1582
- print(f"Updating custom_tskip input with value: {tskip_value}")
1583
- if tskip_value is None:
1584
- return ""
1585
- return str(tskip_value) # Convert to string for the text input
1586
-
1587
- # Add this function to directly set the custom_tskip based on dataset name
1588
- def set_custom_tskip_for_dataset(dataset_name):
1589
- """Set the custom_tskip value based on the selected dataset"""
1590
- if dataset_name is None:
1591
- return 85
1592
- if "butterfly" in dataset_name.lower():
1593
- return 70
1594
- elif "lamp" in dataset_name.lower():
1595
- return 85
1596
- else:
1597
- return 85
1598
-
1599
- if __name__ == "__main__":
1600
- # Uncomment this line to save current results to cache
1601
- #save_current_results_to_cache()
1602
-
1603
- demo = create_gradio_interface()
1604
- demo.launch()
1605
-
1606
- # Add these functions at the top of the file, after the imports and global variables
1607
- # but before any other function definitions
1608
-
1609
- #