Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -35
- CNN/CNN.py +132 -0
- CNN/Confusion Matrixabseline.png +3 -0
- CNN/Evaluate.py +110 -0
- CNN/Evaluate_All.py +186 -0
- CNN/GradCam.py +87 -0
- CNN/Train_Hybrid_CNN.py +117 -0
- CNN/gradcam_pneumoniabaseline.png +3 -0
- CNN/rocbaseline.png +3 -0
- CNN/scorebaeline.txt +22 -0
- Dockerfile +26 -0
- EBMs/Harvest_EBM.py +99 -0
- EBMs/Train_EBM.py +151 -0
- GAN/GAN_Architecture.py +105 -0
- GAN/Harvest_Fakes.py +54 -0
- GAN/Train_GAN.py +119 -0
- baseline_resnet50.pth +3 -0
- gui/backend/main.py +338 -0
- gui/frontend/.gitignore +24 -0
- gui/frontend/dist/assets/index-B4BfuFGE.js +0 -0
- gui/frontend/dist/assets/index-_A223EyE.css +1 -0
- gui/frontend/dist/favicon.svg +1 -0
- gui/frontend/dist/icons.svg +24 -0
- gui/frontend/dist/index.html +15 -0
- gui/frontend/eslint.config.js +21 -0
- gui/frontend/index.html +14 -0
- gui/frontend/node_modules/.bin/acorn +16 -0
- gui/frontend/node_modules/.bin/acorn.cmd +17 -0
- gui/frontend/node_modules/.bin/acorn.ps1 +28 -0
- gui/frontend/node_modules/.bin/baseline-browser-mapping +16 -0
- gui/frontend/node_modules/.bin/baseline-browser-mapping.cmd +17 -0
- gui/frontend/node_modules/.bin/baseline-browser-mapping.ps1 +28 -0
- gui/frontend/node_modules/.bin/browserslist +16 -0
- gui/frontend/node_modules/.bin/browserslist.cmd +17 -0
- gui/frontend/node_modules/.bin/browserslist.ps1 +28 -0
- gui/frontend/node_modules/.bin/eslint +16 -0
- gui/frontend/node_modules/.bin/eslint.cmd +17 -0
- gui/frontend/node_modules/.bin/eslint.ps1 +28 -0
- gui/frontend/node_modules/.bin/jsesc +16 -0
- gui/frontend/node_modules/.bin/jsesc.cmd +17 -0
- gui/frontend/node_modules/.bin/jsesc.ps1 +28 -0
- gui/frontend/node_modules/.bin/json5 +16 -0
- gui/frontend/node_modules/.bin/json5.cmd +17 -0
- gui/frontend/node_modules/.bin/json5.ps1 +28 -0
- gui/frontend/node_modules/.bin/nanoid +16 -0
- gui/frontend/node_modules/.bin/nanoid.cmd +17 -0
- gui/frontend/node_modules/.bin/nanoid.ps1 +28 -0
- gui/frontend/node_modules/.bin/node-which +16 -0
- gui/frontend/node_modules/.bin/node-which.cmd +17 -0
- gui/frontend/node_modules/.bin/node-which.ps1 +28 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,2 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 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
|
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
|
CNN/rocbaseline.png
ADDED
|
Git LFS Details
|
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
|