BrianLov commited on
Commit
068b6e0
·
verified ·
1 Parent(s): d20e486

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -35
  2. CNN/CNN.py +132 -0
  3. CNN/Confusion Matrixabseline.png +3 -0
  4. CNN/Evaluate.py +110 -0
  5. CNN/Evaluate_All.py +186 -0
  6. CNN/GradCam.py +87 -0
  7. CNN/Train_Hybrid_CNN.py +117 -0
  8. CNN/gradcam_pneumoniabaseline.png +3 -0
  9. CNN/rocbaseline.png +3 -0
  10. CNN/scorebaeline.txt +22 -0
  11. Dockerfile +26 -0
  12. EBMs/Harvest_EBM.py +99 -0
  13. EBMs/Train_EBM.py +151 -0
  14. GAN/GAN_Architecture.py +105 -0
  15. GAN/Harvest_Fakes.py +54 -0
  16. GAN/Train_GAN.py +119 -0
  17. baseline_resnet50.pth +3 -0
  18. gui/backend/main.py +338 -0
  19. gui/frontend/.gitignore +24 -0
  20. gui/frontend/dist/assets/index-B4BfuFGE.js +0 -0
  21. gui/frontend/dist/assets/index-_A223EyE.css +1 -0
  22. gui/frontend/dist/favicon.svg +1 -0
  23. gui/frontend/dist/icons.svg +24 -0
  24. gui/frontend/dist/index.html +15 -0
  25. gui/frontend/eslint.config.js +21 -0
  26. gui/frontend/index.html +14 -0
  27. gui/frontend/node_modules/.bin/acorn +16 -0
  28. gui/frontend/node_modules/.bin/acorn.cmd +17 -0
  29. gui/frontend/node_modules/.bin/acorn.ps1 +28 -0
  30. gui/frontend/node_modules/.bin/baseline-browser-mapping +16 -0
  31. gui/frontend/node_modules/.bin/baseline-browser-mapping.cmd +17 -0
  32. gui/frontend/node_modules/.bin/baseline-browser-mapping.ps1 +28 -0
  33. gui/frontend/node_modules/.bin/browserslist +16 -0
  34. gui/frontend/node_modules/.bin/browserslist.cmd +17 -0
  35. gui/frontend/node_modules/.bin/browserslist.ps1 +28 -0
  36. gui/frontend/node_modules/.bin/eslint +16 -0
  37. gui/frontend/node_modules/.bin/eslint.cmd +17 -0
  38. gui/frontend/node_modules/.bin/eslint.ps1 +28 -0
  39. gui/frontend/node_modules/.bin/jsesc +16 -0
  40. gui/frontend/node_modules/.bin/jsesc.cmd +17 -0
  41. gui/frontend/node_modules/.bin/jsesc.ps1 +28 -0
  42. gui/frontend/node_modules/.bin/json5 +16 -0
  43. gui/frontend/node_modules/.bin/json5.cmd +17 -0
  44. gui/frontend/node_modules/.bin/json5.ps1 +28 -0
  45. gui/frontend/node_modules/.bin/nanoid +16 -0
  46. gui/frontend/node_modules/.bin/nanoid.cmd +17 -0
  47. gui/frontend/node_modules/.bin/nanoid.ps1 +28 -0
  48. gui/frontend/node_modules/.bin/node-which +16 -0
  49. gui/frontend/node_modules/.bin/node-which.cmd +17 -0
  50. gui/frontend/node_modules/.bin/node-which.ps1 +28 -0
.gitattributes CHANGED
@@ -1,35 +1,2 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz 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
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
2
+ *.pth filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
CNN/CNN.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.optim as optim
5
+ from torchvision import transforms, models
6
+ import medmnist
7
+ from medmnist import INFO
8
+ from torch.utils.data import DataLoader
9
+ from tqdm import tqdm
10
+ import matplotlib.pyplot as plt
11
+
12
+ def main():
13
+ # 1. Setup and Hardware Configuration
14
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
15
+ print(f"Training on: {device}")
16
+
17
+ # Set this to point to your secondary NVMe drive to prevent OS drive I/O bottlenecks
18
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
19
+ os.makedirs(dataset_root, exist_ok=True)
20
+
21
+ data_flag = 'pneumoniamnist'
22
+ info = INFO[data_flag]
23
+ DataClass = getattr(medmnist, info['python_class'])
24
+
25
+ # 2. The Golden Preprocessing & Dynamic Augmentation
26
+ # We normalize to [-1, 1] using mean=0.5, std=0.5 so it matches your team's generator math
27
+ train_transform = transforms.Compose([
28
+ transforms.Grayscale(num_output_channels=3), # ResNet expects 3 RGB channels
29
+ transforms.RandomHorizontalFlip(), # Dynamic spatial augmentation
30
+ transforms.RandomRotation(10), # Dynamic spatial augmentation
31
+ transforms.ToTensor(),
32
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
33
+ ])
34
+
35
+ val_transform = transforms.Compose([
36
+ transforms.Grayscale(num_output_channels=3),
37
+ transforms.ToTensor(), # NO spatial augmentation for validation
38
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
39
+ ])
40
+
41
+ # 3. Load Datasets
42
+ print("Fetching 224x224 dataset...")
43
+ train_dataset = DataClass(split='train', transform=train_transform, download=True, size=224, root=dataset_root)
44
+ val_dataset = DataClass(split='val', transform=val_transform, download=True, size=224, root=dataset_root)
45
+
46
+ # 4. DataLoaders
47
+ # Using batch size 32. num_workers=0 is the safest default for Windows to prevent multiprocessing crashes.
48
+ train_loader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True, num_workers=0)
49
+ val_loader = DataLoader(dataset=val_dataset, batch_size=32, shuffle=False, num_workers=0)
50
+
51
+ # 5. Initialize ResNet50
52
+ print("Loading ResNet50...")
53
+ model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
54
+
55
+ # Modify the final layer for Binary Classification (Pneumonia vs Normal)
56
+ num_ftrs = model.fc.in_features
57
+ model.fc = nn.Linear(num_ftrs, 2)
58
+ model = model.to(device)
59
+
60
+ # 6. Loss and Optimizer
61
+ criterion = nn.CrossEntropyLoss()
62
+ optimizer = optim.Adam(model.parameters(), lr=1e-4) # 1e-4 is a very stable learning rate for fine-tuning
63
+ num_epochs = 10
64
+
65
+ history_loss = []
66
+ history_acc = []
67
+
68
+ # 7. The Training Loop
69
+ for epoch in range(num_epochs):
70
+ model.train()
71
+ running_loss = 0.0
72
+ correct = 0
73
+ total = 0
74
+
75
+ # tqdm creates a nice progress bar in the terminal
76
+ loop = tqdm(train_loader, leave=True)
77
+ for images, labels in loop:
78
+ images, labels = images.to(device), labels.to(device).squeeze().long()
79
+
80
+ optimizer.zero_grad()
81
+ outputs = model(images)
82
+ loss = criterion(outputs, labels)
83
+ loss.backward()
84
+ optimizer.step()
85
+
86
+ running_loss += loss.item()
87
+ _, predicted = torch.max(outputs.data, 1)
88
+ total += labels.size(0)
89
+ correct += (predicted == labels).sum().item()
90
+
91
+ loop.set_description(f"Epoch [{epoch+1}/{num_epochs}]")
92
+ loop.set_postfix(loss=loss.item(), acc=100.*correct/total)
93
+
94
+ # Calculate the average loss and accuracy for this epoch
95
+ epoch_loss = running_loss / len(train_loader)
96
+ epoch_acc = 100. * correct / total
97
+
98
+ history_loss.append(epoch_loss)
99
+ history_acc.append(epoch_acc)
100
+
101
+ # 8. Save the Frozen Weights for your team
102
+ save_path = os.path.join(dataset_root, 'baseline_resnet50.pth')
103
+ torch.save(model.state_dict(), save_path)
104
+ print(f"\nTraining Complete! Baseline weights saved to: {save_path}")
105
+
106
+ # Create the Learning Curve Graph
107
+ fig, ax1 = plt.subplots(figsize=(10, 6))
108
+
109
+ # Plot Loss (Red Line)
110
+ color = 'tab:red'
111
+ ax1.set_xlabel('Epochs', fontweight='bold')
112
+ ax1.set_ylabel('Training Loss', color=color, fontweight='bold')
113
+ ax1.plot(range(1, num_epochs+1), history_loss, color=color, marker='o', label='Loss')
114
+ ax1.tick_params(axis='y', labelcolor=color)
115
+
116
+ # Plot Accuracy (Blue Line) on the same graph
117
+ ax2 = ax1.twinx()
118
+ color = 'tab:blue'
119
+ ax2.set_ylabel('Training Accuracy (%)', color=color, fontweight='bold')
120
+ ax2.plot(range(1, num_epochs+1), history_acc, color=color, marker='s', label='Accuracy')
121
+ ax2.tick_params(axis='y', labelcolor=color)
122
+
123
+ plt.title('ResNet50 Training Curve', fontsize=14, fontweight='bold')
124
+ fig.tight_layout()
125
+
126
+ # Save the image
127
+ graph_path = os.path.join(dataset_root, 'learning_curve.png')
128
+ plt.savefig(graph_path, dpi=300)
129
+ print(f"Learning Curve saved to: {graph_path}")
130
+
131
+ if __name__ == '__main__':
132
+ main()
CNN/Confusion Matrixabseline.png ADDED

Git LFS Details

  • SHA256: 6d89638aea497d4e6dbb02da45d872b0f90680ba41c3ce299040eb3a109a6a57
  • Pointer size: 130 Bytes
  • Size of remote file: 23.9 kB
CNN/Evaluate.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ from torchvision import transforms, models
5
+ import medmnist
6
+ from medmnist import INFO
7
+ from torch.utils.data import DataLoader
8
+ from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay, roc_curve, auc
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+
12
+ def main():
13
+ # 1. Hardware Setup (Hardware Agnostic)
14
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
15
+ print(f"Evaluating on: {device}")
16
+
17
+ # Streaming from the secondary NVMe
18
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
19
+
20
+ data_flag = 'pneumoniamnist'
21
+ info = INFO[data_flag]
22
+ DataClass = getattr(medmnist, info['python_class'])
23
+
24
+ # 2. Strict Validation Preprocessing
25
+ val_transform = transforms.Compose([
26
+ transforms.Grayscale(num_output_channels=3),
27
+ transforms.ToTensor(),
28
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
29
+ ])
30
+
31
+ # 3. Load the Validation Dataset
32
+ print("Loading Validation Data...")
33
+ val_dataset = DataClass(split='val', transform=val_transform, download=False, size=224, root=dataset_root)
34
+ val_loader = DataLoader(dataset=val_dataset, batch_size=32, shuffle=False, num_workers=0)
35
+
36
+ # 4. Reconstruct and Load the Model
37
+ print("Rebuilding ResNet50 Architecture...")
38
+ model = models.resnet50()
39
+ num_ftrs = model.fc.in_features
40
+ model.fc = nn.Linear(num_ftrs, 2)
41
+
42
+ #Put Model Path here
43
+ weights_path = r"C:\Users\Brian ooi\Documents\code\CVPR\CVPRAssignment\baseline_resnet50.pth"
44
+ model.load_state_dict(torch.load(weights_path, map_location=device, weights_only=True))
45
+ model = model.to(device)
46
+ model.eval()
47
+
48
+ all_predictions = []
49
+ all_true_labels = []
50
+ all_probabilities = [] # Raw probabilities for the ROC curve
51
+
52
+ print("Running Inference...")
53
+ with torch.no_grad():
54
+ for images, labels in val_loader:
55
+ images = images.to(device)
56
+ labels = labels.to(device).squeeze().long()
57
+
58
+ outputs = model(images)
59
+
60
+ # Apply softmax to get percentages (0.0 to 1.0) instead of raw logits
61
+ probabilities = torch.softmax(outputs, dim=1)
62
+ _, predicted = torch.max(outputs.data, 1)
63
+
64
+ all_predictions.extend(predicted.cpu().numpy())
65
+ all_true_labels.extend(labels.cpu().numpy())
66
+ # Save the probability specifically for the "Pneumonia (1)" class
67
+ all_probabilities.extend(probabilities[:, 1].cpu().numpy())
68
+
69
+ # 5. The Clinical Metrics (Sensitivity & Specificity)
70
+ cm = confusion_matrix(all_true_labels, all_predictions)
71
+ tn, fp, fn, tp = cm.ravel()
72
+
73
+ sensitivity = tp / (tp + fn)
74
+ specificity = tn / (tn + fp)
75
+
76
+ print("\n" + "="*50)
77
+ print("CLINICAL PERFORMANCE METRICS")
78
+ print("="*50)
79
+ print(f"Sensitivity (Recall for Pneumonia): {sensitivity:.4f}")
80
+ print(f"Specificity (Recall for Normal): {specificity:.4f}")
81
+ print("="*50)
82
+
83
+ # 6. Generate the ROC Curve
84
+ print("Generating ROC Curve Window...")
85
+ fpr, tpr, thresholds = roc_curve(all_true_labels, all_probabilities)
86
+ roc_auc = auc(fpr, tpr)
87
+
88
+ fig, ax = plt.subplots(figsize=(8, 6))
89
+ ax.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.4f})')
90
+ ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='Random Guessing')
91
+ ax.set_xlim([0.0, 1.0])
92
+ ax.set_ylim([0.0, 1.05])
93
+ ax.set_xlabel('False Positive Rate (1 - Specificity)', fontweight='bold')
94
+ ax.set_ylabel('True Positive Rate (Sensitivity)', fontweight='bold')
95
+ ax.set_title('Receiver Operating Characteristic (ROC) - Baseline ResNet50 (No Augmentation)', fontweight='bold')
96
+ ax.legend(loc="lower right")
97
+
98
+ # Save the ROC curve to your NVMe
99
+ roc_path = os.path.join(dataset_root, 'roc_curve.png')
100
+ plt.savefig(roc_path, dpi=300)
101
+ # 7. Generate and Save the Confusion Matrix Grid
102
+ print("Generating Confusion Matrix Window...")
103
+ disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Normal (0)", "Pneumonia (1)"])
104
+ disp.plot(cmap=plt.cm.Blues)
105
+ plt.title('Baseline ResNet50 (No Augmentation) - PneumoniaMNIST', fontweight='bold')
106
+ plt.savefig(os.path.join(dataset_root, 'baseline_confusion_matrix.png'), dpi=300)
107
+ plt.show()
108
+
109
+ if __name__ == '__main__':
110
+ main()
CNN/Evaluate_All.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ from torchvision import transforms, models
5
+ import medmnist
6
+ from medmnist import INFO
7
+ from torch.utils.data import DataLoader
8
+ from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay, roc_curve, auc
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+
12
+ # ── CONFIG ────────────────────────────────────────────────────────────────────
13
+ PROJECT_ROOT = r"C:\Users\Brian ooi\Documents\code\CVPR\CVPRAssignment"
14
+ RESULTS_ROOT = os.path.join(PROJECT_ROOT, "results_run2")
15
+ FIRST_RESULTS = os.path.join(PROJECT_ROOT, "results")
16
+
17
+ MODELS = {
18
+ "Baseline": {
19
+ "weights": os.path.join(PROJECT_ROOT, "baseline_resnet50.pth"),
20
+ "label": "Baseline ResNet50 (No Augmentation)",
21
+ },
22
+ "GAN": {
23
+ "weights": os.path.join(FIRST_RESULTS, "GAN-20260621T100120Z-3-001", "GAN", "hybrid_resnet50.pth"),
24
+ "label": "Hybrid GAN ResNet50",
25
+ },
26
+ "EBM": {
27
+ "weights": os.path.join(FIRST_RESULTS, "EBM-20260621T100117Z-3-001", "EBM", "hybrid_ebm_resnet50.pth"),
28
+ "label": "Hybrid EBM ResNet50",
29
+ },
30
+ "DiT": {
31
+ "weights": os.path.join(FIRST_RESULTS, "DiT-20260621T100114Z-3-001", "DiT", "hybrid_dit_resnet50.pth"),
32
+ "label": "Hybrid DiT ResNet50",
33
+ },
34
+ "Diffusion": {
35
+ "weights": os.path.join(FIRST_RESULTS, "Diffusion-20260621T100111Z-3-001", "Diffusion", "hybrid_diffusion_resnet50.pth"),
36
+ "label": "Hybrid Diffusion ResNet50",
37
+ },
38
+ "MaskGIT": {
39
+ "weights": os.path.join(FIRST_RESULTS, "MaskGiT-20260621T100123Z-3-001", "MaskGiT", "hybrid_maskgit_resnet50.pth"),
40
+ "label": "Hybrid MaskGIT ResNet50",
41
+ },
42
+ "VAE": {
43
+ "weights": os.path.join(FIRST_RESULTS, "VAE-20260621T100129Z-3-001", "VAE", "hybrid_vae_resnet50.pth"),
44
+ "label": "Hybrid VAE ResNet50",
45
+ },
46
+ }
47
+ # ─────────────────────────────────────────────────────────────────────────────
48
+
49
+ def build_model(device):
50
+ model = models.resnet50()
51
+ model.fc = nn.Linear(model.fc.in_features, 2)
52
+ return model.to(device)
53
+
54
+ def get_val_loader():
55
+ val_transform = transforms.Compose([
56
+ transforms.Grayscale(num_output_channels=3),
57
+ transforms.ToTensor(),
58
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
59
+ ])
60
+ info = INFO["pneumoniamnist"]
61
+ DataClass = getattr(medmnist, info["python_class"])
62
+ val_ds = DataClass(split="val", transform=val_transform,
63
+ download=False, size=224, root=PROJECT_ROOT)
64
+ return DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=0)
65
+
66
+ def evaluate(model, val_loader, device):
67
+ model.eval()
68
+ preds, labels, probs = [], [], []
69
+ with torch.no_grad():
70
+ for imgs, lbls in val_loader:
71
+ imgs = imgs.to(device)
72
+ lbls = lbls.to(device).squeeze().long()
73
+ out = model(imgs)
74
+ prob = torch.softmax(out, dim=1)
75
+ _, pred = torch.max(out, 1)
76
+ preds.extend(pred.cpu().numpy())
77
+ labels.extend(lbls.cpu().numpy())
78
+ probs.extend(prob[:, 1].cpu().numpy())
79
+ return np.array(preds), np.array(labels), np.array(probs)
80
+
81
+ def save_results(name, label, preds, labels, probs, out_dir):
82
+ os.makedirs(out_dir, exist_ok=True)
83
+
84
+ # ── Confusion Matrix ──────────────────────────────────────────────────────
85
+ cm = confusion_matrix(labels, preds)
86
+ tn, fp, fn, tp = cm.ravel()
87
+ disp = ConfusionMatrixDisplay(cm, display_labels=["Normal (0)", "Pneumonia (1)"])
88
+ disp.plot(cmap=plt.cm.Blues)
89
+ plt.title(f"{label} - PneumoniaMNIST", fontweight="bold")
90
+ plt.savefig(os.path.join(out_dir, f"{name}_confusion_matrix.png"), dpi=300, bbox_inches="tight")
91
+ plt.close()
92
+
93
+ # ── ROC Curve ─────────────────────────────────────────────────────────────
94
+ fpr, tpr, _ = roc_curve(labels, probs)
95
+ roc_auc = auc(fpr, tpr)
96
+ fig, ax = plt.subplots(figsize=(8, 6))
97
+ ax.plot(fpr, tpr, color="darkorange", lw=2, label=f"ROC curve (AUC = {roc_auc:.4f})")
98
+ ax.plot([0, 1], [0, 1], color="navy", lw=2, linestyle="--", label="Random Guessing")
99
+ ax.set_xlim([0, 1]); ax.set_ylim([0, 1.05])
100
+ ax.set_xlabel("False Positive Rate (1 - Specificity)", fontweight="bold")
101
+ ax.set_ylabel("True Positive Rate (Sensitivity)", fontweight="bold")
102
+ ax.set_title(f"ROC - {label}", fontweight="bold")
103
+ ax.legend(loc="lower right")
104
+ plt.savefig(os.path.join(out_dir, f"{name}_roc_curve.png"), dpi=300, bbox_inches="tight")
105
+ plt.close()
106
+
107
+ # ── Score TXT ─────────────────────────────────────────────────────────────
108
+ sensitivity = tp / (tp + fn)
109
+ specificity = tn / (tn + fp)
110
+ accuracy = (tp + tn) / (tp + tn + fp + fn)
111
+ report = classification_report(labels, preds, target_names=["Normal (0)", "Pneumonia (1)"])
112
+
113
+ score_path = os.path.join(out_dir, f"{name}_score.txt")
114
+ with open(score_path, "w") as f:
115
+ f.write(f"{'='*50}\n")
116
+ f.write(f"CLASSIFICATION REPORT: {name.upper()}-AUGMENTED MODEL\n")
117
+ f.write(f"{'='*50}\n\n")
118
+ f.write(report)
119
+ f.write(f"\n{'='*50}\n")
120
+ f.write(f"CLINICAL METRICS\n")
121
+ f.write(f"{'='*50}\n")
122
+ f.write(f"Accuracy : {accuracy:.4f} ({accuracy*100:.2f}%)\n")
123
+ f.write(f"Sensitivity : {sensitivity:.4f} ({sensitivity*100:.2f}%)\n")
124
+ f.write(f"Specificity : {specificity:.4f} ({specificity*100:.2f}%)\n")
125
+ f.write(f"AUC : {roc_auc:.4f}\n")
126
+ f.write(f"TN={tn} FP={fp} FN={fn} TP={tp}\n")
127
+
128
+ return {
129
+ "accuracy": accuracy, "sensitivity": sensitivity,
130
+ "specificity": specificity, "auc": roc_auc,
131
+ "tn": tn, "fp": fp, "fn": fn, "tp": tp,
132
+ }
133
+
134
+ def save_summary(summary, out_dir):
135
+ """Save a master comparison table."""
136
+ path = os.path.join(out_dir, "SUMMARY_TABLE.txt")
137
+ with open(path, "w") as f:
138
+ header = f"{'Model':<12} {'Accuracy':>10} {'Sensitivity':>13} {'Specificity':>13} {'AUC':>8} {'TN':>5} {'FP':>5} {'FN':>5} {'TP':>5}"
139
+ f.write("="*len(header) + "\n")
140
+ f.write("MASTER COMPARISON TABLE - PneumoniaMNIST\n")
141
+ f.write("="*len(header) + "\n")
142
+ f.write(header + "\n")
143
+ f.write("-"*len(header) + "\n")
144
+ for name, m in summary.items():
145
+ f.write(
146
+ f"{name:<12} {m['accuracy']*100:>9.2f}% "
147
+ f"{m['sensitivity']*100:>12.2f}% "
148
+ f"{m['specificity']*100:>12.2f}% "
149
+ f"{m['auc']:>8.4f} "
150
+ f"{m['tn']:>5} {m['fp']:>5} {m['fn']:>5} {m['tp']:>5}\n"
151
+ )
152
+ f.write("="*len(header) + "\n")
153
+ print(f"\n✅ Summary table saved → {path}")
154
+
155
+ def main():
156
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
157
+ print(f"Evaluating on: {device}\n")
158
+ val_loader = get_val_loader()
159
+ summary = {}
160
+
161
+ for name, cfg in MODELS.items():
162
+ print(f"── Evaluating: {name} ──────────────────────────")
163
+ if not os.path.exists(cfg["weights"]):
164
+ print(f" ⚠️ Weights not found: {cfg['weights']}\n")
165
+ continue
166
+
167
+ model = build_model(device)
168
+ model.load_state_dict(torch.load(cfg["weights"], map_location=device, weights_only=True))
169
+
170
+ preds, labels, probs = evaluate(model, val_loader, device)
171
+ out_dir = os.path.join(RESULTS_ROOT, name)
172
+ metrics = save_results(name, cfg["label"], preds, labels, probs, out_dir)
173
+ summary[name] = metrics
174
+
175
+ print(f" Accuracy : {metrics['accuracy']*100:.2f}%")
176
+ print(f" Sensitivity: {metrics['sensitivity']*100:.2f}%")
177
+ print(f" Specificity: {metrics['specificity']*100:.2f}%")
178
+ print(f" AUC : {metrics['auc']:.4f}")
179
+ print(f" TN={metrics['tn']} FP={metrics['fp']} FN={metrics['fn']} TP={metrics['tp']}")
180
+ print(f" ✅ Saved → {out_dir}\n")
181
+
182
+ save_summary(summary, RESULTS_ROOT)
183
+ print(f"\n🎉 All done! Results saved to: {RESULTS_ROOT}")
184
+
185
+ if __name__ == "__main__":
186
+ main()
CNN/GradCam.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ from torchvision import transforms, models
5
+ import medmnist
6
+ from medmnist import INFO
7
+ import matplotlib.pyplot as plt
8
+ import numpy as np
9
+ from pytorch_grad_cam import GradCAM
10
+ from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
11
+ from pytorch_grad_cam.utils.image import show_cam_on_image
12
+
13
+ def main():
14
+ # 1. Hardware Setup
15
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
16
+ print(f"Generating Grad-CAM on: {device}")
17
+
18
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
19
+ info = INFO['pneumoniamnist']
20
+ DataClass = getattr(medmnist, info['python_class'])
21
+
22
+ # 2. Rebuild and Load the Frozen Model
23
+ print("Loading Baseline ResNet50...")
24
+ model = models.resnet50()
25
+ model.fc = nn.Linear(model.fc.in_features, 2)
26
+ weights_path = os.path.join(dataset_root, 'baseline_resnet50.pth')
27
+ model.load_state_dict(torch.load(weights_path, map_location=device, weights_only=True))
28
+ model = model.to(device)
29
+ model.eval() # Lock the model
30
+
31
+ # 3. Hook into the final layer
32
+ # Target layer4[-1], which is the final convolutional block before the classification head
33
+ target_layers = [model.layer4[-1]]
34
+ cam = GradCAM(model=model, target_layers=target_layers)
35
+
36
+ # 4. Grab a sample image (without mathematical augmentations)
37
+ val_dataset_raw = DataClass(split='val', download=False, size=224, root=dataset_root)
38
+
39
+ # Mathematical preprocessing just for the model's brain
40
+ transform = transforms.Compose([
41
+ transforms.Grayscale(num_output_channels=3),
42
+ transforms.ToTensor(),
43
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
44
+ ])
45
+
46
+ sample_idx = 0
47
+ for i in range(len(val_dataset_raw)):
48
+ img, label = val_dataset_raw[i]
49
+ if label[0] == 1:
50
+ sample_idx = i
51
+ break
52
+
53
+ raw_img, _ = val_dataset_raw[sample_idx]
54
+
55
+ # Convert the raw grayscale image to RGB and scale to [0, 1] for the visual heatmap overlay
56
+ rgb_img = np.array(raw_img.convert('RGB'), dtype=np.float32) / 255.0
57
+
58
+ # Push the math-ready tensor to the GPU
59
+ input_tensor = transform(raw_img).unsqueeze(0).to(device)
60
+
61
+ # 5. Generate the Heatmap
62
+ # Show pneumonia
63
+ targets = [ClassifierOutputTarget(1)]
64
+ grayscale_cam = cam(input_tensor=input_tensor, targets=targets)[0, :]
65
+
66
+ # Overlay the red/yellow heatmap on the black and white X-ray
67
+ visualization = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True)
68
+
69
+ # 6. Plot and Save for the Report
70
+ print("Generating Figure...")
71
+ fig, axes = plt.subplots(1, 2, figsize=(10, 5))
72
+ axes[0].imshow(rgb_img)
73
+ axes[0].set_title("Original X-Ray (Pneumonia)", fontweight='bold')
74
+ axes[0].axis('off')
75
+
76
+ axes[1].imshow(visualization)
77
+ axes[1].set_title("Grad-CAM Heatmap", fontweight='bold')
78
+ axes[1].axis('off')
79
+
80
+ save_path = os.path.join(dataset_root, 'gradcam_pneumonia.png')
81
+ plt.tight_layout()
82
+ plt.savefig(save_path, dpi=300)
83
+ print(f"Success! Image saved to: {save_path}")
84
+ plt.show()
85
+
86
+ if __name__ == '__main__':
87
+ main()
CNN/Train_Hybrid_CNN.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.optim as optim
5
+ from torchvision import transforms, models
6
+ from torch.utils.data import DataLoader, Dataset, ConcatDataset
7
+ from PIL import Image
8
+ import medmnist
9
+ from medmnist import INFO
10
+ from tqdm import tqdm
11
+ import numpy as np
12
+
13
+ # 1. Custom Dataset Loader for GAN Images
14
+ class SyntheticDataset(Dataset):
15
+ def __init__(self, folder_path, transform=None):
16
+ self.folder_path = folder_path
17
+ self.transform = transform
18
+ self.image_files = [f for f in os.listdir(folder_path) if f.endswith('.png')]
19
+
20
+ def __len__(self):
21
+ return len(self.image_files)
22
+
23
+ def __getitem__(self, idx):
24
+ img_path = os.path.join(self.folder_path, self.image_files[idx])
25
+ # Force load as grayscale to match the real X-rays perfectly
26
+ image = Image.open(img_path).convert('L')
27
+
28
+ if self.transform:
29
+ image = self.transform(image)
30
+
31
+ # Explicitly assign the "Normal (0)" label in the exact format MedMNIST uses
32
+ label = np.array([0])
33
+ return image, label
34
+
35
+ def main():
36
+ # 2. Hardware Setup
37
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
38
+ print(f"Training Hybrid CNN on: {device}")
39
+
40
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
41
+ synthetic_folder = os.path.join(dataset_root, "MaskGIT_Synthetic", "Normal_0")
42
+
43
+ # 3. Strict Preprocessing Pipeline
44
+ train_transform = transforms.Compose([
45
+ transforms.Resize(224),
46
+ transforms.RandomHorizontalFlip(),
47
+ transforms.RandomRotation(10),
48
+ # Convert the 1-channel X-ray to 3-channel RGB for the ResNet
49
+ transforms.Grayscale(num_output_channels=3),
50
+ transforms.ToTensor(),
51
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
52
+ ])
53
+
54
+ # 4. Load the Real Dataset
55
+ print("Loading Real MedMNIST Data...")
56
+ info = INFO['pneumoniamnist']
57
+ DataClass = getattr(medmnist, info['python_class'])
58
+ real_train_dataset = DataClass(split='train', transform=train_transform, download=False, size=224, root=dataset_root)
59
+
60
+ # 5. Load the Synthetic Dataset
61
+ print("Loading Synthetic GAN Data...")
62
+ synthetic_train_dataset = SyntheticDataset(folder_path=synthetic_folder, transform=train_transform)
63
+
64
+ # 6. Data Fusion (Stitching them together)
65
+ print("Fusing Datasets into Hybrid Structure...")
66
+ hybrid_dataset = ConcatDataset([real_train_dataset, synthetic_train_dataset])
67
+
68
+ # Num workers = 0 to prevent Windows I/O crashes
69
+ train_loader = DataLoader(dataset=hybrid_dataset, batch_size=32, shuffle=True, num_workers=0)
70
+
71
+ print(f"Total Training Images: {len(hybrid_dataset)} (Balanced Class Distribution)")
72
+
73
+ # 7. Initialize a Fresh ResNet50
74
+ print("Initializing fresh ResNet50 architecture...")
75
+ model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
76
+ num_ftrs = model.fc.in_features
77
+ model.fc = nn.Linear(num_ftrs, 2)
78
+ model = model.to(device)
79
+
80
+ # 8. Training Parameters
81
+ criterion = nn.CrossEntropyLoss()
82
+ optimizer = optim.Adam(model.parameters(), lr=1e-4)
83
+ num_epochs = 10
84
+
85
+ # 9. The Training Loop
86
+ for epoch in range(num_epochs):
87
+ model.train()
88
+ running_loss = 0.0
89
+ correct = 0
90
+ total = 0
91
+
92
+ loop = tqdm(train_loader, leave=True)
93
+ for images, labels in loop:
94
+ images = images.to(device)
95
+ labels = labels.to(device).squeeze().long()
96
+
97
+ optimizer.zero_grad()
98
+ outputs = model(images)
99
+ loss = criterion(outputs, labels)
100
+ loss.backward()
101
+ optimizer.step()
102
+
103
+ running_loss += loss.item()
104
+ _, predicted = torch.max(outputs.data, 1)
105
+ total += labels.size(0)
106
+ correct += (predicted == labels).sum().item()
107
+
108
+ loop.set_description(f"Epoch [{epoch+1}/{num_epochs}]")
109
+ loop.set_postfix(loss=loss.item(), acc=100.*correct/total)
110
+
111
+ # 10. Save the Final Hybrid Brain
112
+ save_path = os.path.join(dataset_root, 'hybrid_maskgit_resnet50.pth')
113
+ torch.save(model.state_dict(), save_path)
114
+ print(f"\nHybrid Training Complete! Weights saved to: {save_path}")
115
+
116
+ if __name__ == '__main__':
117
+ main()
CNN/gradcam_pneumoniabaseline.png ADDED

Git LFS Details

  • SHA256: 333fe2f5d012979dbadf35c83054f00acf4c4def38c155eb07e9e05dad04d76a
  • Pointer size: 131 Bytes
  • Size of remote file: 216 kB
CNN/rocbaseline.png ADDED

Git LFS Details

  • SHA256: 6374943239055c1cc944184ec6be03994c98f13fc0313eee8f6d1722da972228
  • Pointer size: 130 Bytes
  • Size of remote file: 40.7 kB
CNN/scorebaeline.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ==================================================
2
+
3
+ CLASSIFICATION REPORT
4
+
5
+ ==================================================
6
+
7
+ precision recall f1-score support
8
+
9
+
10
+
11
+ Normal (0) 0.99 0.93 0.96 135
12
+
13
+ Pneumonia (1) 0.97 1.00 0.99 389
14
+
15
+
16
+
17
+ accuracy 0.98 524
18
+
19
+ macro avg 0.98 0.96 0.97 524
20
+
21
+ weighted avg 0.98 0.98 0.98 524
22
+
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install dependencies first (for faster caching)
6
+ COPY hf_requirements.txt .
7
+ RUN pip install --no-cache-dir -r hf_requirements.txt
8
+
9
+ # Copy only the necessary code and weights
10
+ # We copy gui/, CNN/, EBM/, VAE/ because main.py imports from them
11
+ COPY gui/ ./gui/
12
+ COPY CNN/ ./CNN/
13
+ COPY EBM/ ./EBM/
14
+ COPY VAE/ ./VAE/
15
+
16
+ # Copy the pre-generated images and metrics
17
+ COPY results/ ./results/
18
+
19
+ # Copy the model weights needed for live inference
20
+ COPY *.pth ./
21
+
22
+ # Hugging Face Spaces require web apps to run on port 7860
23
+ EXPOSE 7860
24
+
25
+ # Run FastAPI
26
+ CMD ["uvicorn", "gui.backend.main:app", "--host", "0.0.0.0", "--port", "7860"]
EBMs/Harvest_EBM.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ from torchvision.utils import save_image
5
+ from tqdm import tqdm
6
+
7
+ # 1. Rebuild the Exact EBM Architecture
8
+ class EnergyModel(nn.Module):
9
+ def __init__(self):
10
+ super(EnergyModel, self).__init__()
11
+ self.net = nn.Sequential(
12
+ nn.Conv2d(1, 32, 4, 2, 1),
13
+ nn.LeakyReLU(0.2, inplace=True),
14
+ nn.Conv2d(32, 64, 4, 2, 1),
15
+ nn.LeakyReLU(0.2, inplace=True),
16
+ nn.Conv2d(64, 128, 4, 2, 1),
17
+ nn.LeakyReLU(0.2, inplace=True),
18
+ nn.Conv2d(128, 256, 4, 2, 1),
19
+ nn.LeakyReLU(0.2, inplace=True),
20
+ nn.Conv2d(256, 512, 4, 2, 1),
21
+ nn.LeakyReLU(0.2, inplace=True),
22
+ nn.Flatten(),
23
+ nn.Linear(512 * 7 * 7, 1)
24
+ )
25
+
26
+ def forward(self, x):
27
+ return self.net(x)
28
+
29
+ # 2. The Langevin Sampler (Requires gradients to simulate physics!)
30
+ def sample_langevin(model, x, steps=100, step_size=10, noise_scale=0.005):
31
+ x = x.clone().detach().requires_grad_(True)
32
+
33
+ # Enable gradients here, even if the main loop is evaluating
34
+ with torch.enable_grad():
35
+ for _ in range(steps):
36
+ energy = model(x)
37
+ grad = torch.autograd.grad(energy.sum(), x, only_inputs=True)[0]
38
+
39
+ # Move down the energy slope, add thermodynamic noise
40
+ x.data -= step_size * grad + noise_scale * torch.randn_like(x)
41
+ x.data = torch.clamp(x.data, -1.0, 1.0)
42
+
43
+ return x.detach()
44
+
45
+ def main():
46
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
47
+ print(f"Targeting device for EBM Harvest: {device}")
48
+
49
+ # Windows path
50
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
51
+ output_dir = os.path.join(dataset_root, "EBM_Synthetic", "Normal_0")
52
+ os.makedirs(output_dir, exist_ok=True)
53
+
54
+ # 3. Load the Thermodynamic Brain
55
+ print("Loading EBM weights...")
56
+ model = EnergyModel().to(device)
57
+ weight_path = os.path.join(dataset_root, "EBM_Outputs", "ebm_baseline.pth")
58
+
59
+ if not os.path.exists(weight_path):
60
+ print(f"Error: Weights not found at {weight_path}")
61
+ return
62
+
63
+ model.load_state_dict(torch.load(weight_path, map_location=device, weights_only=True))
64
+ model.eval()
65
+
66
+ # 4. Harvest Parameters
67
+ total_images_needed = 2600
68
+ batch_size = 64 # Pushing batch size up to maximize CUDA parallelization
69
+ generated_count = 0
70
+
71
+ print(f"Commencing Langevin thermodynamic sampling for {total_images_needed} lungs...")
72
+ print("Warning: This will take significantly longer than GAN/VAE harvesting!")
73
+
74
+ # No torch.no_grad() globally because Langevin dynamics require gradients
75
+ with tqdm(total=total_images_needed, desc="Cooling Static") as pbar:
76
+ while generated_count < total_images_needed:
77
+ current_batch_size = min(batch_size, total_images_needed - generated_count)
78
+
79
+ # Start with pure random static [-1, 1]
80
+ initial_noise = (torch.rand(current_batch_size, 1, 224, 224, device=device) * 2 - 1)
81
+
82
+ # Run the physics simulation (100 steps for higher fidelity)
83
+ fake_images = sample_langevin(model, initial_noise, steps=100)
84
+
85
+ # Denormalize from [-1, 1] back to [0, 1]
86
+ fake_images = (fake_images + 1) / 2.0
87
+
88
+ # Save the synthesized lungs
89
+ for i in range(current_batch_size):
90
+ img_path = os.path.join(output_dir, f"ebm_normal_{generated_count}.png")
91
+ save_image(fake_images[i], img_path)
92
+ generated_count += 1
93
+
94
+ pbar.update(current_batch_size)
95
+
96
+ print(f"\nSuccess! EBM Harvest complete. Images stored in: {output_dir}")
97
+
98
+ if __name__ == "__main__":
99
+ main()
EBMs/Train_EBM.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.optim as optim
5
+ from torchvision import transforms
6
+ from torchvision.utils import save_image
7
+ from torch.utils.data import DataLoader, Subset
8
+ import medmnist
9
+ from medmnist import INFO
10
+ from tqdm import tqdm
11
+
12
+ # ==========================================
13
+ # 1. The Energy Function (A simple CNN)
14
+ # ==========================================
15
+ # This network's only job is to output a single scalar "Energy" score.
16
+ # Low score = Real Lung. High score = Fake/Noise.
17
+ class EnergyModel(nn.Module):
18
+ def __init__(self):
19
+ super(EnergyModel, self).__init__()
20
+ self.net = nn.Sequential(
21
+ nn.Conv2d(1, 32, 4, 2, 1),
22
+ nn.LeakyReLU(0.2, inplace=True),
23
+ nn.Conv2d(32, 64, 4, 2, 1),
24
+ nn.LeakyReLU(0.2, inplace=True),
25
+ nn.Conv2d(64, 128, 4, 2, 1),
26
+ nn.LeakyReLU(0.2, inplace=True),
27
+ nn.Conv2d(128, 256, 4, 2, 1),
28
+ nn.LeakyReLU(0.2, inplace=True),
29
+ nn.Conv2d(256, 512, 4, 2, 1),
30
+ nn.LeakyReLU(0.2, inplace=True),
31
+ nn.Flatten(),
32
+ nn.Linear(512 * 7 * 7, 1) # Output a single number
33
+ )
34
+
35
+ def forward(self, x):
36
+ return self.net(x)
37
+
38
+ # ==========================================
39
+ # 2. Langevin Dynamics (The Thermodynamic Generator)
40
+ # ==========================================
41
+ def sample_langevin(model, x, steps=60, step_size=10, noise_scale=0.005):
42
+ # This is Markov Chain Monte Carlo (MCMC)
43
+ # Detach x to start a fresh computational graph
44
+ x = x.clone().detach().requires_grad_(True)
45
+
46
+ for _ in range(steps):
47
+ # Calculate the energy of the current image
48
+ energy = model(x)
49
+
50
+ # Sum the energy so autograd can compute gradients for the whole batch at once
51
+ grad = torch.autograd.grad(energy.sum(), x, only_inputs=True)[0]
52
+
53
+ # Langevin Equation:
54
+ # Move pixels in the OPPOSITE direction of the gradient (to lower the energy)
55
+ # Add a tiny bit of random thermodynamic heat (noise) to prevent getting stuck
56
+ x.data -= step_size * grad + noise_scale * torch.randn_like(x)
57
+
58
+ # Clamp pixels to stay within valid grayscale image bounds [-1, 1]
59
+ x.data = torch.clamp(x.data, -1.0, 1.0)
60
+
61
+ return x.detach() # Strip the autograd receipt so it doesn't cause an OOM!
62
+
63
+ # ==========================================
64
+ # 3. The Training Loop
65
+ # ==========================================
66
+ def main():
67
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
68
+ print(f"Igniting Thermodynamic EBM on: {device}")
69
+
70
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
71
+ out_dir = os.path.join(dataset_root, "EBM_Outputs")
72
+ os.makedirs(out_dir, exist_ok=True)
73
+
74
+ # We scale to [-1, 1] for stable gradient flows in Langevin dynamics
75
+ transform = transforms.Compose([
76
+ transforms.Resize(224),
77
+ transforms.ToTensor(),
78
+ transforms.Normalize(mean=[0.5], std=[0.5])
79
+ ])
80
+
81
+ print("Loading Normal Lungs...")
82
+ info = INFO['pneumoniamnist']
83
+ DataClass = getattr(medmnist, info['python_class'])
84
+ full_dataset = DataClass(split='train', transform=transform, download=False, size=224, root=dataset_root)
85
+
86
+ # Isolate healthy lungs
87
+ normal_indices = [i for i in range(len(full_dataset)) if full_dataset[i][1][0] == 0]
88
+ normal_dataset = Subset(full_dataset, normal_indices)
89
+ dataloader = DataLoader(normal_dataset, batch_size=32, shuffle=True, num_workers=0)
90
+
91
+ model = EnergyModel().to(device)
92
+ optimizer = optim.Adam(model.parameters(), lr=1e-4)
93
+
94
+ num_epochs = 100
95
+ print("Commencing Energy Optimization...")
96
+
97
+ for epoch in range(num_epochs):
98
+ model.train()
99
+ loop = tqdm(dataloader, leave=True)
100
+
101
+ for real_images, _ in loop:
102
+ real_images = real_images.to(device)
103
+ batch_size = real_images.size(0)
104
+
105
+ # 1. Start with pure random static
106
+ initial_noise = torch.rand_like(real_images) * 2 - 1
107
+
108
+ # 2. Cool the static down into fake lungs via Langevin Dynamics
109
+ fake_images = sample_langevin(model, initial_noise, steps=60)
110
+
111
+ optimizer.zero_grad()
112
+
113
+ # 3. Calculate Energy for both Real and Fake
114
+ real_energy = model(real_images)
115
+ fake_energy = model(fake_images)
116
+
117
+ # 4. Contrastive Divergence Loss
118
+ # Real Energy low (negative), Fake Energy high (positive)
119
+ loss = real_energy.mean() - fake_energy.mean()
120
+
121
+ # Add L2 Regularization (Prevents the energy values from exploding to infinity)
122
+ loss += 0.001 * (real_energy ** 2 + fake_energy ** 2).mean()
123
+
124
+ loss.backward()
125
+
126
+ # Gradient clipping to prevent thermodynamic explosions
127
+ torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
128
+ optimizer.step()
129
+
130
+ loop.set_description(f"EBM Epoch [{epoch+1}/{num_epochs}]")
131
+ loop.set_postfix(Loss=loss.item(), Real_E=real_energy.mean().item(), Fake_E=fake_energy.mean().item())
132
+
133
+ # Save visual progression
134
+ if (epoch + 1) % 10 == 0:
135
+ model.eval()
136
+ print(f"\nGenerating checkpoint samples for Epoch {epoch+1}...")
137
+ with torch.no_grad():
138
+ # Use gradients to sample, enable grad temporarily
139
+ with torch.enable_grad():
140
+ test_noise = (torch.rand(16, 1, 224, 224, device=device) * 2 - 1)
141
+ test_samples = sample_langevin(model, test_noise, steps=100)
142
+
143
+ # Denormalize from [-1, 1] back to [0, 1] for saving
144
+ test_samples = (test_samples + 1) / 2.0
145
+ save_image(test_samples, os.path.join(out_dir, f'ebm_sample_{epoch+1}.png'), nrow=4)
146
+
147
+ torch.save(model.state_dict(), os.path.join(out_dir, 'ebm_baseline.pth'))
148
+ print("\nEBM Training Complete.")
149
+
150
+ if __name__ == '__main__':
151
+ main()
GAN/GAN_Architecture.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ class Generator(nn.Module):
5
+ def __init__(self, latent_dim=100):
6
+ super(Generator, self).__init__()
7
+
8
+ # Mapping the 100-dimension noise vector to a 7x7 spatial foundation
9
+ self.init_size = 7
10
+ self.l1 = nn.Sequential(nn.Linear(latent_dim, 256 * self.init_size ** 2))
11
+
12
+ # Using kernel=4, stride=2, padding=1 perfectly doubles the resolution at each step
13
+ self.conv_blocks = nn.Sequential(
14
+ nn.BatchNorm2d(256),
15
+
16
+ # 7x7 -> 14x14
17
+ nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1),
18
+ nn.BatchNorm2d(128),
19
+ nn.LeakyReLU(0.2, inplace=True),
20
+
21
+ # 14x14 -> 28x28
22
+ nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1),
23
+ nn.BatchNorm2d(64),
24
+ nn.LeakyReLU(0.2, inplace=True),
25
+
26
+ # 28x28 -> 56x56
27
+ nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1),
28
+ nn.BatchNorm2d(32),
29
+ nn.LeakyReLU(0.2, inplace=True),
30
+
31
+ # 56x56 -> 112x112
32
+ nn.ConvTranspose2d(32, 16, kernel_size=4, stride=2, padding=1),
33
+ nn.BatchNorm2d(16),
34
+ nn.LeakyReLU(0.2, inplace=True),
35
+
36
+ # 112x112 -> 224x224
37
+ # Output is 1 channel (Grayscale) and uses Tanh to map pixels to [-1, 1]
38
+ nn.ConvTranspose2d(16, 1, kernel_size=4, stride=2, padding=1),
39
+ nn.Tanh()
40
+ )
41
+
42
+ def forward(self, z):
43
+ out = self.l1(z)
44
+ out = out.view(out.shape[0], 256, self.init_size, self.init_size)
45
+ img = self.conv_blocks(out)
46
+ return img
47
+
48
+
49
+ class Discriminator(nn.Module):
50
+ def __init__(self):
51
+ super(Discriminator, self).__init__()
52
+
53
+ def discriminator_block(in_filters, out_filters, bn=True):
54
+ block = [
55
+ # Wrap the convolution in spectral normalization
56
+ nn.utils.spectral_norm(nn.Conv2d(in_filters, out_filters, kernel_size=4, stride=2, padding=1)),
57
+ nn.LeakyReLU(0.2, inplace=True),
58
+ nn.Dropout2d(0.25)
59
+ ]
60
+ if bn:
61
+ block.append(nn.BatchNorm2d(out_filters, 0.8))
62
+ return block
63
+
64
+ self.model = nn.Sequential(
65
+ # Input: 1 x 224 x 224
66
+ *discriminator_block(1, 16, bn=False), # 112x112
67
+ *discriminator_block(16, 32), # 56x56
68
+ *discriminator_block(32, 64), # 28x28
69
+ *discriminator_block(64, 128), # 14x14
70
+ *discriminator_block(128, 256), # 7x7
71
+ )
72
+
73
+ # The downsampled image is flattened and fed into a single neuron to guess: Real or Fake?
74
+ ds_size = 7
75
+ self.adv_layer = nn.Sequential(
76
+ nn.Linear(256 * ds_size ** 2, 1),
77
+ nn.Sigmoid()
78
+ )
79
+
80
+ def forward(self, img):
81
+ out = self.model(img)
82
+ out = out.view(out.shape[0], -1)
83
+ validity = self.adv_layer(out)
84
+ return validity
85
+
86
+ if __name__ == "__main__":
87
+ print("Testing GAN Dimensions...")
88
+
89
+ # 1. Create a dummy noise vector (Batch Size of 2, 100 random numbers each)
90
+ latent_dim = 100
91
+ z = torch.randn(2, latent_dim)
92
+
93
+ # 2. Test Generator
94
+ gen = Generator(latent_dim)
95
+ fake_imgs = gen(z)
96
+ print(f"Generator Output Shape: {fake_imgs.shape}")
97
+ # EXPECTED: [2, 1, 224, 224] (2 images, 1 channel, 224x224 pixels)
98
+
99
+ # 3. Test Discriminator
100
+ disc = Discriminator()
101
+ validity = disc(fake_imgs)
102
+ print(f"Discriminator Output Shape: {validity.shape}")
103
+ # EXPECTED: [2, 1] (2 guesses between 0.0 and 1.0)
104
+
105
+ print("If you see [2, 1, 224, 224] and [2, 1], the architecture is perfectly locked in!")
GAN/Harvest_Fakes.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from torchvision.utils import save_image
4
+ from tqdm import tqdm
5
+
6
+ # Import the blueprint
7
+ from GAN_Architecture import Generator
8
+
9
+ def main():
10
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
11
+ print(f"Harvesting synthetic data on: {device}")
12
+
13
+ # Set up the exact folder structure needed for easy PyTorch loading later
14
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
15
+ synthetic_dir = os.path.join(dataset_root, "Synthetic_Data", "Normal_0")
16
+ os.makedirs(synthetic_dir, exist_ok=True)
17
+
18
+ # 1. Load the frozen brain
19
+ print("Waking up the trained Generator...")
20
+ latent_dim = 100
21
+ generator = Generator(latent_dim=latent_dim).to(device)
22
+
23
+ weights_path = os.path.join(dataset_root, 'generator_weights.pth')
24
+ generator.load_state_dict(torch.load(weights_path, map_location=device, weights_only=True))
25
+ generator.eval() # CRITICAL: Lock the gradients
26
+
27
+ # 2. Harvesting Parameters
28
+ num_images_needed = 2600
29
+ batch_size = 100
30
+ batches = num_images_needed // batch_size
31
+
32
+ print(f"Generating {num_images_needed} high-resolution synthetic X-rays...")
33
+
34
+ img_counter = 0
35
+ with torch.no_grad():
36
+ for i in tqdm(range(batches), desc="Harvesting Batches"):
37
+ # Generate pure noise
38
+ z = torch.randn(batch_size, latent_dim, device=device)
39
+
40
+ # Pass noise through the GAN to hallucinate the images
41
+ fake_imgs = generator(z)
42
+
43
+ # Save each image individually to the NVMe drive
44
+ for j in range(fake_imgs.size(0)):
45
+ # Un-normalize from [-1, 1] back to standard [0, 1] pixel values
46
+ save_path = os.path.join(synthetic_dir, f"synthetic_normal_{img_counter}.png")
47
+ save_image(fake_imgs[j], save_path, normalize=True)
48
+ img_counter += 1
49
+
50
+ print(f"\nHarvest Complete! {img_counter} synthetic healthy lungs saved to:")
51
+ print(synthetic_dir)
52
+
53
+ if __name__ == '__main__':
54
+ main()
GAN/Train_GAN.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.optim as optim
5
+ from torchvision import transforms
6
+ from torch.utils.data import DataLoader, Subset
7
+ import torchvision.utils as vutils
8
+ import medmnist
9
+ from medmnist import INFO
10
+ from tqdm import tqdm
11
+
12
+ # Import your blueprints
13
+ from GAN_Architecture import Generator, Discriminator
14
+
15
+ def main():
16
+ # 1. Hardware & Setup
17
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
+ print(f"Training GAN on: {device}")
19
+
20
+ dataset_root = r"C:\Users\USER\Downloads\MedMNIST_Data"
21
+ os.makedirs(os.path.join(dataset_root, "GAN_Outputs"), exist_ok=True)
22
+
23
+ # 2. Hyperparameters (The standard for stable GANs)
24
+ latent_dim = 100
25
+ lr = 0.0002
26
+ b1 = 0.5
27
+ b2 = 0.999
28
+ num_epochs = 300 # Pushed from 50 to 300
29
+ batch_size = 128 # Taking advantage of the VRAM
30
+
31
+ # 3. Load & Filter the Dataset (CRITICAL STEP)
32
+ # Only grayscale images mathematically bounded to [-1, 1]
33
+ transform = transforms.Compose([
34
+ transforms.ToTensor(),
35
+ transforms.Normalize(mean=[0.5], std=[0.5])
36
+ ])
37
+
38
+ info = INFO['pneumoniamnist']
39
+ DataClass = getattr(medmnist, info['python_class'])
40
+
41
+ print("Loading dataset and isolating 'Normal (0)' lungs...")
42
+ full_dataset = DataClass(split='train', transform=transform, download=False, size=224, root=dataset_root)
43
+
44
+ # Filter out all Pneumonia (1) images.
45
+ normal_indices = [i for i in range(len(full_dataset)) if full_dataset[i][1][0] == 0]
46
+ normal_dataset = Subset(full_dataset, normal_indices)
47
+
48
+ # num_workers=0 to prevent Windows multiprocessing crashes
49
+ # Pass the new batch_size variable here
50
+ dataloader = DataLoader(normal_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
51
+ print(f"Isolated {len(normal_dataset)} healthy lung images for training.")
52
+
53
+ # 4. Initialize Networks
54
+ generator = Generator(latent_dim).to(device)
55
+ discriminator = Discriminator().to(device)
56
+
57
+ # 5. Loss & Optimizers
58
+ adversarial_loss = nn.BCELoss() # Binary Cross Entropy
59
+ optimizer_G = optim.Adam(generator.parameters(), lr=lr, betas=(b1, b2))
60
+ optimizer_D = optim.Adam(discriminator.parameters(), lr=lr, betas=(b1, b2))
61
+
62
+ # Create a static noise vector to visually track how the Generator improves over time
63
+ fixed_noise = torch.randn(16, latent_dim, device=device)
64
+
65
+ # 6. The Arena (Training Loop)
66
+ for epoch in range(num_epochs):
67
+ loop = tqdm(dataloader, leave=True)
68
+ for i, (imgs, _) in enumerate(loop):
69
+
70
+ # Ground truth labels (Real = 1.0, Fake = 0.0)
71
+ # Change from 1.0 to 0.9 for real images
72
+ valid = torch.full((imgs.size(0), 1), 0.9, device=device, requires_grad=False)
73
+ # Change from 0.0 to 0.1 for fake images
74
+ fake = torch.full((imgs.size(0), 1), 0.1, device=device, requires_grad=False)
75
+
76
+ real_imgs = imgs.to(device)
77
+
78
+ # -----------------
79
+ # Train Generator
80
+ # -----------------
81
+ optimizer_G.zero_grad()
82
+
83
+ # Generate a batch of images from random noise
84
+ z = torch.randn(imgs.size(0), latent_dim, device=device)
85
+ gen_imgs = generator(z)
86
+
87
+ # The Generator's goal is to trick the Discriminator into guessing 'valid' (1.0)
88
+ g_loss = adversarial_loss(discriminator(gen_imgs), valid)
89
+ g_loss.backward()
90
+ optimizer_G.step()
91
+
92
+ # ---------------------
93
+ # Train Discriminator
94
+ # ---------------------
95
+ optimizer_D.zero_grad()
96
+
97
+ real_loss = adversarial_loss(discriminator(real_imgs), valid)
98
+ fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)
99
+
100
+ d_loss = (real_loss + fake_loss) / 2
101
+ d_loss.backward()
102
+ optimizer_D.step()
103
+
104
+ loop.set_description(f"Epoch [{epoch+1}/{num_epochs}]")
105
+ loop.set_postfix(D_loss=d_loss.item(), G_loss=g_loss.item())
106
+
107
+ # Save a visual sample of the hallucinated lungs every 5 epochs
108
+ if (epoch + 1) % 5 == 0:
109
+ with torch.no_grad():
110
+ sample_imgs = generator(fixed_noise)
111
+ # Un-normalize from [-1, 1] back to [0, 1] for saving as a standard image file
112
+ vutils.save_image(sample_imgs, os.path.join(dataset_root, "GAN_Outputs", f"epoch_{epoch+1}.png"), nrow=4, normalize=True)
113
+
114
+ # 7. Save the final Generator Brain
115
+ torch.save(generator.state_dict(), os.path.join(dataset_root, 'generator_weights.pth'))
116
+ print("GAN Training Complete! Generator saved.")
117
+
118
+ if __name__ == '__main__':
119
+ main()
baseline_resnet50.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6924d57f89b224f0118469826ceca25a6b4ea61e8ad22345d1cd88dd799e87a3
3
+ size 94369787
gui/backend/main.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import base64
3
+ import math
4
+ import random
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+ from torchvision import transforms, models
12
+ from PIL import Image
13
+
14
+ from fastapi import FastAPI, File, UploadFile, Form
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.responses import JSONResponse
17
+
18
+ # ── Paths ─────────────────────────────────────────────────────────────────────
19
+ PROJECT_ROOT = Path(r"C:\Users\Brian ooi\Documents\code\CVPR\CVPRAssignment")
20
+ RESULTS = PROJECT_ROOT / "results"
21
+
22
+ CLASSIFIER_WEIGHTS = {
23
+ "Baseline": PROJECT_ROOT / "baseline_resnet50.pth",
24
+ "GAN": RESULTS / "GAN-20260621T100120Z-3-001/GAN/hybrid_resnet50.pth",
25
+ "EBM": RESULTS / "EBM-20260621T100117Z-3-001/EBM/hybrid_ebm_resnet50.pth",
26
+ "DiT": RESULTS / "DiT-20260621T100114Z-3-001/DiT/hybrid_dit_resnet50.pth",
27
+ "Diffusion": RESULTS / "Diffusion-20260621T100111Z-3-001/Diffusion/hybrid_diffusion_resnet50.pth",
28
+ "MaskGIT": RESULTS / "MaskGiT-20260621T100123Z-3-001/MaskGiT/hybrid_maskgit_resnet50.pth",
29
+ "VAE": RESULTS / "VAE-20260621T100129Z-3-001/VAE/hybrid_vae_resnet50.pth",
30
+ }
31
+
32
+ GAN_GEN_PATH = RESULTS / "GAN-20260621T100120Z-3-001/GAN/generator_weights.pth"
33
+ EBM_PATH = RESULTS / "EBM-20260621T100117Z-3-001/EBM/EBM_Outputs/ebm_baseline.pth"
34
+ VAE_PATH = RESULTS / "VAE-20260621T100129Z-3-001/VAE/vae_baseline.pth"
35
+
36
+ # Pre-generated epoch sample grid image directories (instant serving, no inference)
37
+ SAMPLE_DIRS = {
38
+ "EBM": RESULTS / "EBM-20260621T100117Z-3-001/EBM/EBM_Outputs",
39
+ "DiT": RESULTS / "DiT-20260621T100114Z-3-001/DiT/DiT_Outputs",
40
+ "Diffusion":RESULTS / "Diffusion-20260621T100111Z-3-001/Diffusion/Diffusion_Outputs",
41
+ "MaskGIT": RESULTS / "MaskGiT-20260621T100123Z-3-001/MaskGiT/MaskGIT_Outputs",
42
+ }
43
+
44
+ # ── GAN Architecture ──────────────────────────────────────────────────────────
45
+ class Generator(nn.Module):
46
+ def __init__(self, latent_dim=100):
47
+ super().__init__()
48
+ self.init_size = 7
49
+ self.l1 = nn.Sequential(nn.Linear(latent_dim, 256 * self.init_size ** 2))
50
+ self.conv_blocks = nn.Sequential(
51
+ nn.BatchNorm2d(256),
52
+ nn.ConvTranspose2d(256, 128, 4, 2, 1), nn.BatchNorm2d(128), nn.LeakyReLU(0.2, inplace=True),
53
+ nn.ConvTranspose2d(128, 64, 4, 2, 1), nn.BatchNorm2d(64), nn.LeakyReLU(0.2, inplace=True),
54
+ nn.ConvTranspose2d(64, 32, 4, 2, 1), nn.BatchNorm2d(32), nn.LeakyReLU(0.2, inplace=True),
55
+ nn.ConvTranspose2d(32, 16, 4, 2, 1), nn.BatchNorm2d(16), nn.LeakyReLU(0.2, inplace=True),
56
+ nn.ConvTranspose2d(16, 1, 4, 2, 1), nn.Tanh(),
57
+ )
58
+ def forward(self, z):
59
+ out = self.l1(z)
60
+ out = out.view(out.shape[0], 256, self.init_size, self.init_size)
61
+ return self.conv_blocks(out)
62
+
63
+ # ── EBM Architecture ──────────────────────────────────────────────────────────
64
+ class EnergyModel(nn.Module):
65
+ def __init__(self):
66
+ super().__init__()
67
+ self.net = nn.Sequential(
68
+ nn.Conv2d(1, 32, 4, 2, 1), nn.LeakyReLU(0.2, inplace=True),
69
+ nn.Conv2d(32, 64, 4, 2, 1), nn.LeakyReLU(0.2, inplace=True),
70
+ nn.Conv2d(64, 128, 4, 2, 1), nn.LeakyReLU(0.2, inplace=True),
71
+ nn.Conv2d(128, 256, 4, 2, 1), nn.LeakyReLU(0.2, inplace=True),
72
+ nn.Conv2d(256, 512, 4, 2, 1), nn.LeakyReLU(0.2, inplace=True),
73
+ nn.Flatten(), nn.Linear(512 * 7 * 7, 1),
74
+ )
75
+ def forward(self, x):
76
+ return self.net(x)
77
+
78
+ def sample_langevin(model, x, steps=25, step_size=10, noise_scale=0.005):
79
+ x = x.clone().detach().requires_grad_(True)
80
+ with torch.enable_grad():
81
+ for _ in range(steps):
82
+ energy = model(x)
83
+ grad = torch.autograd.grad(energy.sum(), x, only_inputs=True)[0]
84
+ x.data -= step_size * grad + noise_scale * torch.randn_like(x)
85
+ x.data = torch.clamp(x.data, -1.0, 1.0)
86
+ return x.detach()
87
+
88
+ # ── VAE Architecture ──────────────────────────────────────────────────────────
89
+ class VAE(nn.Module):
90
+ def __init__(self, latent_dim=128):
91
+ super().__init__()
92
+ self.enc1 = nn.Conv2d(1, 32, 4, 2, 1)
93
+ self.enc2 = nn.Conv2d(32, 64, 4, 2, 1)
94
+ self.enc3 = nn.Conv2d(64, 128, 4, 2, 1)
95
+ self.enc4 = nn.Conv2d(128, 256, 4, 2, 1)
96
+ self.enc5 = nn.Conv2d(256, 512, 4, 2, 1)
97
+ self.fc_mu = nn.Linear(512 * 7 * 7, latent_dim)
98
+ self.fc_logvar = nn.Linear(512 * 7 * 7, latent_dim)
99
+ self.dec_fc = nn.Linear(latent_dim, 512 * 7 * 7)
100
+ self.dec1 = nn.ConvTranspose2d(512, 256, 4, 2, 1)
101
+ self.dec2 = nn.ConvTranspose2d(256, 128, 4, 2, 1)
102
+ self.dec3 = nn.ConvTranspose2d(128, 64, 4, 2, 1)
103
+ self.dec4 = nn.ConvTranspose2d(64, 32, 4, 2, 1)
104
+ self.dec5 = nn.ConvTranspose2d(32, 1, 4, 2, 1)
105
+
106
+ def decode(self, z):
107
+ x = F.relu(self.dec_fc(z))
108
+ x = x.view(x.size(0), 512, 7, 7)
109
+ x = F.relu(self.dec1(x))
110
+ x = F.relu(self.dec2(x))
111
+ x = F.relu(self.dec3(x))
112
+ x = F.relu(self.dec4(x))
113
+ return torch.sigmoid(self.dec5(x))
114
+
115
+ def forward(self, x):
116
+ x = F.relu(self.enc1(x)); x = F.relu(self.enc2(x))
117
+ x = F.relu(self.enc3(x)); x = F.relu(self.enc4(x))
118
+ x = F.relu(self.enc5(x)); x = x.view(x.size(0), -1)
119
+ mu, logvar = self.fc_mu(x), self.fc_logvar(x)
120
+ std = torch.exp(0.5 * logvar)
121
+ z = mu + std * torch.randn_like(std)
122
+ return self.decode(z), mu, logvar
123
+
124
+ # ── Model Cache ───────────────────────────────────────────────────────────────
125
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
126
+ _cache: dict = {}
127
+
128
+ def get_gan():
129
+ if "gan" not in _cache:
130
+ gen = Generator(latent_dim=100).to(device)
131
+ gen.load_state_dict(torch.load(GAN_GEN_PATH, map_location=device, weights_only=True))
132
+ gen.eval(); _cache["gan"] = gen
133
+ return _cache["gan"]
134
+
135
+ def get_ebm():
136
+ if "ebm" not in _cache:
137
+ ebm = EnergyModel().to(device)
138
+ ebm.load_state_dict(torch.load(EBM_PATH, map_location=device, weights_only=True))
139
+ ebm.eval(); _cache["ebm"] = ebm
140
+ return _cache["ebm"]
141
+
142
+ def get_vae():
143
+ if "vae" not in _cache:
144
+ vae = VAE(latent_dim=128).to(device)
145
+ vae.load_state_dict(torch.load(VAE_PATH, map_location=device, weights_only=True))
146
+ vae.eval(); _cache["vae"] = vae
147
+ return _cache["vae"]
148
+
149
+ def get_classifier(name: str):
150
+ key = f"clf_{name}"
151
+ if key not in _cache:
152
+ m = models.resnet50()
153
+ m.fc = nn.Linear(m.fc.in_features, 2)
154
+ m.load_state_dict(torch.load(CLASSIFIER_WEIGHTS[name], map_location=device, weights_only=True))
155
+ m.eval(); m.to(device); _cache[key] = m
156
+ return _cache[key]
157
+
158
+ # ── Helpers ───────────────────────────────────────────────────────────────────
159
+ def tensor_to_b64(t: torch.Tensor) -> str:
160
+ arr = t.squeeze().cpu().numpy()
161
+ arr = np.clip(arr * 255, 0, 255).astype(np.uint8)
162
+ pil = Image.fromarray(arr, mode="L").convert("RGB")
163
+ buf = io.BytesIO(); pil.save(buf, format="PNG")
164
+ return base64.b64encode(buf.getvalue()).decode()
165
+
166
+ def pil_to_b64(pil: Image.Image) -> str:
167
+ pil = pil.convert("RGB")
168
+ buf = io.BytesIO(); pil.save(buf, format="PNG")
169
+ return base64.b64encode(buf.getvalue()).decode()
170
+
171
+ def grid_png_to_tiles(path: Path, n: int = 3) -> list[str]:
172
+ """Crop n individual tiles from a 4x4 grid image and return as base64 list."""
173
+ img = Image.open(path).convert("RGB")
174
+ w, h = img.size
175
+ cols, rows = 4, 4
176
+ tw, th = w // cols, h // rows
177
+ tiles = []
178
+ positions = random.sample(range(cols * rows), min(n, cols * rows))
179
+ for pos in positions:
180
+ r, c = divmod(pos, cols)
181
+ tile = img.crop((c * tw, r * th, (c+1) * tw, (r+1) * th))
182
+ tile = tile.resize((224, 224), Image.LANCZOS)
183
+ buf = io.BytesIO(); tile.save(buf, format="PNG")
184
+ tiles.append(base64.b64encode(buf.getvalue()).decode())
185
+ return tiles
186
+
187
+ def get_sample_tiles(model_key: str, n: int = 3) -> list[str]:
188
+ """Return n sample images from pre-generated epoch outputs."""
189
+ sample_dir = SAMPLE_DIRS.get(model_key)
190
+ if not sample_dir or not sample_dir.exists():
191
+ return []
192
+ pngs = list(sample_dir.glob("*.png"))
193
+ # Prefer later epoch samples (higher quality)
194
+ pngs.sort()
195
+ # Pick from the last half of available samples
196
+ best = pngs[len(pngs)//2:] if len(pngs) > 1 else pngs
197
+ chosen = random.sample(best, min(n, len(best)))
198
+ results = []
199
+ for p in chosen:
200
+ results.extend(grid_png_to_tiles(p, n=1))
201
+ if len(results) >= n:
202
+ break
203
+ return results[:n]
204
+
205
+ # ── App ───────────────────────────────────────────────────────────────────────
206
+ app = FastAPI(title="CVPR Medical Imaging GUI")
207
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
208
+
209
+ @app.get("/health")
210
+ def health():
211
+ return {"status": "ok", "device": str(device)}
212
+
213
+ @app.get("/metrics_images")
214
+ def metrics_images():
215
+ baseline_cm = RESULTS / "CNN-20260621T100109Z-3-001/CNN/Confusion Matrixabseline.png"
216
+ gan_cm = RESULTS / "GAN-20260621T100120Z-3-001/GAN/hybrid_confusion_matrix.png"
217
+ ebm_cm = RESULTS / "EBM-20260621T100117Z-3-001/EBM/hybrid_ebm_confusion_matrix.png"
218
+
219
+ baseline_roc = RESULTS / "CNN-20260621T100109Z-3-001/CNN/rocbaseline.png"
220
+ gan_roc = RESULTS / "GAN-20260621T100120Z-3-001/GAN/roc_curvehybrid.png"
221
+ ebm_roc = RESULTS / "EBM-20260621T100117Z-3-001/EBM/roc_curveebm.png"
222
+
223
+ def to_b64(path):
224
+ if not path.exists(): return None
225
+ return pil_to_b64(Image.open(path))
226
+
227
+ return {
228
+ "Baseline": {"cm": to_b64(baseline_cm), "roc": to_b64(baseline_roc)},
229
+ "GAN": {"cm": to_b64(gan_cm), "roc": to_b64(gan_roc)},
230
+ "EBM": {"cm": to_b64(ebm_cm), "roc": to_b64(ebm_roc)},
231
+ }
232
+
233
+ @app.get("/dataset_samples")
234
+ def dataset_samples():
235
+ import medmnist
236
+ from medmnist import PneumoniaMNIST
237
+ dataset = PneumoniaMNIST(split='test', download=True)
238
+ normal_imgs = []
239
+ pneumonia_imgs = []
240
+
241
+ for img, label in dataset:
242
+ if label[0] == 0 and len(normal_imgs) < 3:
243
+ normal_imgs.append(pil_to_b64(img))
244
+ elif label[0] == 1 and len(pneumonia_imgs) < 3:
245
+ pneumonia_imgs.append(pil_to_b64(img))
246
+ if len(normal_imgs) == 3 and len(pneumonia_imgs) == 3:
247
+ break
248
+
249
+ return {"normal": normal_imgs, "pneumonia": pneumonia_imgs}
250
+
251
+ @app.post("/augment")
252
+ async def augment(file: UploadFile = File(...)):
253
+ raw = await file.read()
254
+ pil = Image.open(io.BytesIO(raw)).convert("L").resize((224, 224), Image.LANCZOS)
255
+ aug_list = [
256
+ ("Original", transforms.Compose([transforms.Resize((224, 224))])),
257
+ ("H-Flip", transforms.Compose([transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=1.0)])),
258
+ ("Rotation ±15°", transforms.Compose([transforms.Resize((224, 224)), transforms.RandomRotation(15)])),
259
+ ("Brightness", transforms.Compose([transforms.Resize((224, 224)), transforms.ColorJitter(brightness=0.5, contrast=0.4)])),
260
+ ("Gaussian Blur", transforms.Compose([transforms.Resize((224, 224)), transforms.GaussianBlur(kernel_size=11, sigma=(2, 4))])),
261
+ ("Random Crop", transforms.Compose([transforms.Resize((256, 256)), transforms.RandomCrop(224)])),
262
+ ]
263
+ results = [{"label": lbl, "image": pil_to_b64(t(pil))} for lbl, t in aug_list]
264
+ return {"augmentations": results}
265
+
266
+ @app.post("/generate_all")
267
+ async def generate_all(n: int = 3):
268
+ """Generate/retrieve n samples from all 6 generative models."""
269
+ n = min(max(n, 1), 4)
270
+ output = {}
271
+
272
+ # GAN — live generation (fast)
273
+ gen = get_gan()
274
+ z = torch.randn(n, 100, device=device)
275
+ with torch.no_grad():
276
+ fake = gen(z)
277
+ output["GAN"] = [tensor_to_b64((fake[i] + 1) / 2.0) for i in range(n)]
278
+
279
+ # VAE — live generation (just decoder, very fast)
280
+ vae = get_vae()
281
+ z_vae = torch.randn(n, 128, device=device)
282
+ with torch.no_grad():
283
+ vae_out = vae.decode(z_vae)
284
+ output["VAE"] = [tensor_to_b64(vae_out[i]) for i in range(n)]
285
+
286
+ # EBM — serve from pre-generated epoch samples (Langevin is slow on CPU)
287
+ output["EBM"] = get_sample_tiles("EBM", n)
288
+
289
+ # DiT — serve from pre-generated epoch samples (1000-step denoising is slow)
290
+ output["DiT"] = get_sample_tiles("DiT", n)
291
+
292
+ # Diffusion — serve from pre-generated epoch samples
293
+ output["Diffusion"] = get_sample_tiles("Diffusion", n)
294
+
295
+ # MaskGIT — serve from pre-generated epoch samples
296
+ output["MaskGIT"] = get_sample_tiles("MaskGIT", n)
297
+
298
+ return output
299
+
300
+ # Keep the old /generate endpoint for backward compatibility
301
+ @app.post("/generate")
302
+ async def generate(n: int = 3):
303
+ n = min(max(n, 1), 6)
304
+ gen = get_gan()
305
+ z = torch.randn(n, 100, device=device)
306
+ with torch.no_grad():
307
+ fake = gen(z)
308
+ gan_imgs = [tensor_to_b64((fake[i] + 1) / 2.0) for i in range(n)]
309
+ ebm = get_ebm()
310
+ noise = torch.rand(n, 1, 224, 224, device=device) * 2 - 1
311
+ ebm_raw = sample_langevin(ebm, noise, steps=25)
312
+ ebm_imgs = [tensor_to_b64((ebm_raw[i] + 1) / 2.0) for i in range(n)]
313
+ return {"gan": gan_imgs, "ebm": ebm_imgs}
314
+
315
+ @app.post("/classify")
316
+ async def classify(file: UploadFile = File(...), model_name: str = Form("Baseline")):
317
+ if model_name not in CLASSIFIER_WEIGHTS:
318
+ return JSONResponse(status_code=400, content={"error": f"Unknown model: {model_name}"})
319
+ raw = await file.read()
320
+ pil = Image.open(io.BytesIO(raw)).convert("L")
321
+ t = transforms.Compose([
322
+ transforms.Grayscale(num_output_channels=3),
323
+ transforms.Resize((224, 224)),
324
+ transforms.ToTensor(),
325
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
326
+ ])
327
+ tensor = t(pil).unsqueeze(0).to(device)
328
+ clf = get_classifier(model_name)
329
+ with torch.no_grad():
330
+ prob = torch.softmax(clf(tensor), dim=1).cpu().numpy()[0]
331
+ idx = int(np.argmax(prob))
332
+ return {
333
+ "model": model_name,
334
+ "prediction": "Normal" if idx == 0 else "Pneumonia",
335
+ "confidence": float(prob[idx]),
336
+ "prob_normal": float(prob[0]),
337
+ "prob_pneumonia": float(prob[1]),
338
+ }
gui/frontend/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
gui/frontend/dist/assets/index-B4BfuFGE.js ADDED
The diff for this file is too large to render. See raw diff
 
gui/frontend/dist/assets/index-_A223EyE.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap";*,:before,:after{box-sizing:border-box}body{margin:0}:root{--bg:#060b17;--surface:#0d1526;--surface2:#111d35;--border:#4f8ef726;--blue:#4f8ef7;--purple:#a855f7;--green:#22c55e;--red:#ef4444;--text:#e2e8f0;--muted:#64748b;--radius:14px}*,:before,:after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth}body{background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;min-height:100vh;font-family:Inter,sans-serif}.hero{text-align:center;border-bottom:1px solid var(--border);padding:64px 24px 56px;position:relative;overflow:hidden}.hero-glow{pointer-events:none;background:radial-gradient(80% 60% at 50% -20%,#4f8ef72e 0%,#0000 70%);position:absolute;inset:0}.hero-badge{letter-spacing:.1em;text-transform:uppercase;color:var(--blue);background:#4f8ef714;border:1px solid #4f8ef759;border-radius:99px;margin-bottom:20px;padding:4px 14px;font-size:11px;font-weight:600;display:inline-block}.hero h1{color:#fff;margin-bottom:16px;font-size:clamp(28px,5vw,52px);font-weight:800;line-height:1.15}.gradient-text{background:linear-gradient(135deg, var(--blue), var(--purple));-webkit-text-fill-color:transparent;-webkit-background-clip:text;background-clip:text}.hero-sub{color:var(--muted);letter-spacing:.04em;font-size:14px}.main{flex-direction:column;gap:28px;max-width:1100px;margin:0 auto;padding:40px 24px 80px;display:flex}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px;transition:border-color .2s}.card:hover{border-color:#4f8ef74d}.card-header{align-items:center;gap:12px;margin-bottom:20px;display:flex}.card-header h2{color:#fff;font-size:18px;font-weight:700}.card-desc{color:var(--muted);margin-bottom:20px;font-size:13px;line-height:1.6}.badge{letter-spacing:.06em;text-transform:uppercase;color:var(--blue);white-space:nowrap;background:#4f8ef71f;border:1px solid #4f8ef740;border-radius:99px;padding:3px 10px;font-size:11px;font-weight:600}.upload-zone{text-align:center;cursor:pointer;background:#4f8ef70a;border:2px dashed #4f8ef759;border-radius:12px;padding:48px 24px;transition:all .25s}.upload-zone:hover,.upload-zone.dragging{border-color:var(--blue);background:#4f8ef717;transform:scale(1.005)}.upload-zone.has-preview{padding:16px}.upload-icon{margin-bottom:12px;font-size:48px}.upload-title{color:#fff;margin-bottom:6px;font-size:16px;font-weight:600}.upload-sub{color:var(--muted);font-size:13px}.preview-wrap{display:inline-block;position:relative}.preview-img{border-radius:8px;max-height:220px;margin:0 auto;display:block}.preview-change{color:#fff;opacity:0;background:#0000008c;border-radius:0 0 8px 8px;padding:6px;font-size:12px;transition:opacity .2s;position:absolute;bottom:0;left:0;right:0}.preview-wrap:hover .preview-change{opacity:1}.img-grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:14px;display:grid}.img-card{background:var(--surface2);border:1px solid var(--border);border-radius:10px;transition:transform .2s,border-color .2s;overflow:hidden}.img-card:hover{border-color:#4f8ef766;transform:translateY(-3px)}.img-card img{aspect-ratio:1;object-fit:cover;width:100%;display:block}.img-label{color:var(--muted);text-align:center;padding:6px 4px;font-size:11px;font-weight:500;display:block}.gen-all-grid{grid-template-columns:repeat(3,1fr);gap:16px;margin-top:24px;display:grid}.model-col{background:var(--surface2);border:1px solid var(--border);border-radius:12px;transition:border-color .2s,transform .2s;overflow:hidden}.model-col:hover{transform:translateY(-2px)}.model-col-header{border-left:3px solid;border-bottom:1px solid var(--border);background:#ffffff05;padding:12px 14px}.model-name{letter-spacing:.05em;text-transform:uppercase;margin-bottom:3px;font-size:13px;font-weight:700;display:block}.model-desc{color:var(--muted);font-size:10px;line-height:1.4;display:block}.model-img-stack{flex-direction:column;gap:8px;padding:10px;display:flex}.img-placeholder{aspect-ratio:1;background:#ffffff0a;border:1px dashed #ffffff14;border-radius:8px;width:100%}.gen-badge{color:#10b981!important;background:#10b9811f!important;border-color:#10b9814d!important}.spinner-label{color:var(--muted);text-align:center;margin-top:10px;font-size:12px}@media (width<=900px){.gen-all-grid{grid-template-columns:repeat(2,1fr)}}@media (width<=580px){.gen-all-grid{grid-template-columns:1fr}}.btn-primary{background:linear-gradient(135deg, var(--blue), var(--purple));color:#fff;cursor:pointer;border:none;border-radius:10px;padding:11px 24px;font-family:inherit;font-size:14px;font-weight:600;transition:opacity .2s,transform .15s}.btn-primary:hover:not(:disabled){opacity:.88;transform:translateY(-1px)}.btn-primary:disabled{opacity:.45;cursor:not-allowed}.clf-controls{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.select-wrap select{background:var(--surface2);color:var(--text);border:1px solid var(--border);cursor:pointer;border-radius:10px;outline:none;min-width:150px;padding:10px 16px;font-family:inherit;font-size:14px;transition:border-color .2s}.select-wrap select:focus{border-color:var(--blue)}.result-box{border:1px solid;border-radius:12px;margin-top:20px;padding:20px}.result-box.normal{background:#22c55e0f;border-color:#22c55e59}.result-box.pneumonia{background:#ef44440f;border-color:#ef444459}.result-header{align-items:center;gap:14px;margin-bottom:16px;display:flex}.result-icon{font-size:28px}.result-label{color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-size:11px}.result-pred{color:#fff;font-size:22px;font-weight:800}.result-model-badge{color:var(--blue);background:#4f8ef71f;border:1px solid #4f8ef740;border-radius:99px;margin-left:auto;padding:4px 12px;font-size:11px;font-weight:600}.conf-bars{flex-direction:column;gap:10px;display:flex}.conf-row{align-items:center;gap:10px;display:flex}.conf-label{width:80px;color:var(--muted);font-size:13px;font-weight:500}.conf-track{background:#ffffff14;border-radius:99px;flex:1;height:8px;overflow:hidden}.conf-fill{border-radius:99px;height:100%;transition:width .6s cubic-bezier(.4,0,.2,1)}.conf-pct{text-align:right;width:48px;color:var(--text);font-size:13px;font-weight:600}.table-wrap{overflow-x:auto}.results-table{border-collapse:collapse;width:100%;font-size:13px}.results-table th{text-align:left;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);border-bottom:1px solid var(--border);padding:10px 14px;font-size:11px;font-weight:600}.results-table td{border-bottom:1px solid #ffffff0a;padding:12px 14px}.results-table tr:last-child td{border-bottom:none}.results-table tr:hover td{background:#4f8ef70d}.row-best td{background:#4f8ef712}.row-best td:first-child{border-left:3px solid var(--blue)}.cell-best,.cell-fp-low{color:#22d3a5;font-weight:700}.cell-worst{color:#f87171;font-weight:600}.table-model-name{align-items:center;gap:8px;display:flex}.table-dot{border-radius:50%;flex-shrink:0;width:8px;height:8px}.trophy{color:var(--blue);background:linear-gradient(135deg,#4f8ef733,#a855f733);border:1px solid #4f8ef766;border-radius:99px;margin-left:4px;padding:2px 8px;font-size:11px;font-weight:700}.spinner-wrap{justify-content:center;padding:32px;display:flex}.spinner{border:3px solid #4f8ef733;border-top-color:var(--blue);border-radius:50%;width:36px;height:36px;animation:.75s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.empty{color:var(--muted);text-align:center;padding:32px 0;font-size:13px}.footer{border-top:1px solid var(--border);text-align:center;color:var(--muted);padding:20px;font-size:12px}@media (width<=640px){.gen-grid{grid-template-columns:1fr}.gen-divider{display:none}.clf-controls{flex-direction:column;align-items:stretch}}
gui/frontend/dist/favicon.svg ADDED
gui/frontend/dist/icons.svg ADDED
gui/frontend/dist/index.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>CVPR — Generative Medical Imaging Augmentation</title>
8
+ <meta name="description" content="Interactive dashboard for generative medical imaging augmentation using GAN, EBM, Diffusion, DiT, MaskGIT, and VAE on PneumoniaMNIST." />
9
+ <script type="module" crossorigin src="/assets/index-B4BfuFGE.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-_A223EyE.css">
11
+ </head>
12
+ <body>
13
+ <div id="root"></div>
14
+ </body>
15
+ </html>
gui/frontend/eslint.config.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['dist']),
9
+ {
10
+ files: ['**/*.{js,jsx}'],
11
+ extends: [
12
+ js.configs.recommended,
13
+ reactHooks.configs.flat.recommended,
14
+ reactRefresh.configs.vite,
15
+ ],
16
+ languageOptions: {
17
+ globals: globals.browser,
18
+ parserOptions: { ecmaFeatures: { jsx: true } },
19
+ },
20
+ },
21
+ ])
gui/frontend/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>CVPR — Generative Medical Imaging Augmentation</title>
8
+ <meta name="description" content="Interactive dashboard for generative medical imaging augmentation using GAN, EBM, Diffusion, DiT, MaskGIT, and VAE on PneumoniaMNIST." />
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="/src/main.jsx"></script>
13
+ </body>
14
+ </html>
gui/frontend/node_modules/.bin/acorn ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../acorn/bin/acorn" "$@"
14
+ else
15
+ exec node "$basedir/../acorn/bin/acorn" "$@"
16
+ fi
gui/frontend/node_modules/.bin/acorn.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\acorn\bin\acorn" %*
gui/frontend/node_modules/.bin/acorn.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../acorn/bin/acorn" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../acorn/bin/acorn" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../acorn/bin/acorn" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../acorn/bin/acorn" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret
gui/frontend/node_modules/.bin/baseline-browser-mapping ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
14
+ else
15
+ exec node "$basedir/../baseline-browser-mapping/dist/cli.cjs" "$@"
16
+ fi
gui/frontend/node_modules/.bin/baseline-browser-mapping.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\baseline-browser-mapping\dist\cli.cjs" %*
gui/frontend/node_modules/.bin/baseline-browser-mapping.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../baseline-browser-mapping/dist/cli.cjs" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret
gui/frontend/node_modules/.bin/browserslist ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../browserslist/cli.js" "$@"
14
+ else
15
+ exec node "$basedir/../browserslist/cli.js" "$@"
16
+ fi
gui/frontend/node_modules/.bin/browserslist.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\browserslist\cli.js" %*
gui/frontend/node_modules/.bin/browserslist.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../browserslist/cli.js" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../browserslist/cli.js" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../browserslist/cli.js" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret
gui/frontend/node_modules/.bin/eslint ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../eslint/bin/eslint.js" "$@"
14
+ else
15
+ exec node "$basedir/../eslint/bin/eslint.js" "$@"
16
+ fi
gui/frontend/node_modules/.bin/eslint.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\eslint\bin\eslint.js" %*
gui/frontend/node_modules/.bin/eslint.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../eslint/bin/eslint.js" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../eslint/bin/eslint.js" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../eslint/bin/eslint.js" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../eslint/bin/eslint.js" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret
gui/frontend/node_modules/.bin/jsesc ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../jsesc/bin/jsesc" "$@"
14
+ else
15
+ exec node "$basedir/../jsesc/bin/jsesc" "$@"
16
+ fi
gui/frontend/node_modules/.bin/jsesc.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jsesc\bin\jsesc" %*
gui/frontend/node_modules/.bin/jsesc.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../jsesc/bin/jsesc" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../jsesc/bin/jsesc" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../jsesc/bin/jsesc" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret
gui/frontend/node_modules/.bin/json5 ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../json5/lib/cli.js" "$@"
14
+ else
15
+ exec node "$basedir/../json5/lib/cli.js" "$@"
16
+ fi
gui/frontend/node_modules/.bin/json5.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\json5\lib\cli.js" %*
gui/frontend/node_modules/.bin/json5.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../json5/lib/cli.js" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../json5/lib/cli.js" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../json5/lib/cli.js" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret
gui/frontend/node_modules/.bin/nanoid ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
14
+ else
15
+ exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
16
+ fi
gui/frontend/node_modules/.bin/nanoid.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*
gui/frontend/node_modules/.bin/nanoid.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret
gui/frontend/node_modules/.bin/node-which ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*|*MINGW*|*MSYS*)
6
+ if command -v cygpath > /dev/null 2>&1; then
7
+ basedir=`cygpath -w "$basedir"`
8
+ fi
9
+ ;;
10
+ esac
11
+
12
+ if [ -x "$basedir/node" ]; then
13
+ exec "$basedir/node" "$basedir/../which/bin/node-which" "$@"
14
+ else
15
+ exec node "$basedir/../which/bin/node-which" "$@"
16
+ fi
gui/frontend/node_modules/.bin/node-which.cmd ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @ECHO off
2
+ GOTO start
3
+ :find_dp0
4
+ SET dp0=%~dp0
5
+ EXIT /b
6
+ :start
7
+ SETLOCAL
8
+ CALL :find_dp0
9
+
10
+ IF EXIST "%dp0%\node.exe" (
11
+ SET "_prog=%dp0%\node.exe"
12
+ ) ELSE (
13
+ SET "_prog=node"
14
+ SET PATHEXT=%PATHEXT:;.JS;=;%
15
+ )
16
+
17
+ endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\which\bin\node-which" %*
gui/frontend/node_modules/.bin/node-which.ps1 ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env pwsh
2
+ $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
+
4
+ $exe=""
5
+ if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
6
+ # Fix case when both the Windows and Linux builds of Node
7
+ # are installed in the same directory
8
+ $exe=".exe"
9
+ }
10
+ $ret=0
11
+ if (Test-Path "$basedir/node$exe") {
12
+ # Support pipeline input
13
+ if ($MyInvocation.ExpectingInput) {
14
+ $input | & "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
15
+ } else {
16
+ & "$basedir/node$exe" "$basedir/../which/bin/node-which" $args
17
+ }
18
+ $ret=$LASTEXITCODE
19
+ } else {
20
+ # Support pipeline input
21
+ if ($MyInvocation.ExpectingInput) {
22
+ $input | & "node$exe" "$basedir/../which/bin/node-which" $args
23
+ } else {
24
+ & "node$exe" "$basedir/../which/bin/node-which" $args
25
+ }
26
+ $ret=$LASTEXITCODE
27
+ }
28
+ exit $ret