k23064919 commited on
Commit
5acdcb2
·
2 Parent(s): bb82af6 e4229f6

Merge branch 'develop'

Browse files
.gitignore CHANGED
@@ -1,10 +1,8 @@
1
- <<<<<<< HEAD
2
  .vscode/
3
  .venv/
4
  .vscode/
5
  .models/
6
  __pycache__/
7
- =======
8
 
9
  # Python environment
10
  venv/
@@ -18,4 +16,4 @@ __pycache__/
18
 
19
  # Generated files from data_preparation.py
20
  class_distribution.png
21
- >>>>>>> 04cb88662062ef6b880c627546d067fa0cedfa8b
 
 
1
  .vscode/
2
  .venv/
3
  .vscode/
4
  .models/
5
  __pycache__/
 
6
 
7
  # Python environment
8
  venv/
 
16
 
17
  # Generated files from data_preparation.py
18
  class_distribution.png
19
+
best_model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3c19d6a5fea8043e6fda261763b7909aaed487b83991f29ca395b2ce7c8e591
3
+ size 20532322
dataPrep/data_preparation.py CHANGED
@@ -75,15 +75,15 @@ task.connect({
75
  })
76
 
77
  # ----- Load a subset from a given dataset & track with ClearML -----
78
- data_plants, subset_dataset, features = make_subset(
79
- DATASET_LINK, DATASET_SUBSET_RATIO, task
80
  )
81
 
82
 
83
  # ---- Exploratory data analysis (EDA) ----
84
 
85
  # Reformatting the label feature to understand bias
86
- labels_list = subset_dataset['label']
87
  df_labels = pd.Series(labels_list)
88
  label_count = df_labels.value_counts(sort=False)
89
 
@@ -112,7 +112,6 @@ clearml_logger.report_scalar(
112
  value=(max_count / min_count),
113
  iteration=1
114
  )
115
-
116
  print("--- Class imbalance analysis --- ")
117
  print(f"Max labels in a class: {max_count}")
118
  print(f"Min labels in a class: {min_count}")
@@ -124,17 +123,16 @@ class_names = features['label'].names
124
  formatted_class_names = [" ".join(name.replace('_', ' ').split()) for name in class_names]
125
  label_count.index = formatted_class_names
126
 
127
- # Plotting class distribution
128
  plt.figure(figsize=(10,6))
129
  label_count.plot(kind='bar', color='skyblue')
130
- plt.title("Class Distribution in Subset Dataset")
131
  plt.xlabel("Class")
132
  plt.ylabel("Count")
133
  plt.tight_layout()
134
 
135
  clearml_logger.report_matplotlib_figure(
136
  title="EDA Class Distribution",
137
- series="Subset Dataset",
138
  figure=plt.gcf(),
139
  iteration=1
140
  )
@@ -152,7 +150,7 @@ if __name__ == "__main__":
152
  }
153
 
154
  prototype_loaders = make_dataset_loaders(
155
- subset_dataset, SEED, BATCH_SIZE, TEST_SIZE, aug_config
156
  )
157
 
158
  print("\n--- Handoff Test Successful ---")
@@ -176,9 +174,14 @@ if __name__ == "__main__":
176
  print(f"Validation loader batches: {len(final_loaders['val'])}")
177
  print(f"Test loader batches: {len(final_loaders['test'])}")
178
 
 
 
 
 
 
 
179
 
 
180
  # Close the ClearML task
181
- task.mark_completed()
182
  task.close()
183
-
184
  print("\n--- Script Finished ---")
 
75
  })
76
 
77
  # ----- Load a subset from a given dataset & track with ClearML -----
78
+ data_plants, prototyping_dataset, features, clearml_dataset = make_subset(
79
+ DATASET_LINK, DATASET_SUBSET_RATIO, clearml_logger
80
  )
81
 
82
 
83
  # ---- Exploratory data analysis (EDA) ----
84
 
85
  # Reformatting the label feature to understand bias
86
+ labels_list = prototyping_dataset['label']
87
  df_labels = pd.Series(labels_list)
88
  label_count = df_labels.value_counts(sort=False)
89
 
 
112
  value=(max_count / min_count),
113
  iteration=1
114
  )
 
115
  print("--- Class imbalance analysis --- ")
116
  print(f"Max labels in a class: {max_count}")
117
  print(f"Min labels in a class: {min_count}")
 
123
  formatted_class_names = [" ".join(name.replace('_', ' ').split()) for name in class_names]
124
  label_count.index = formatted_class_names
125
 
 
126
  plt.figure(figsize=(10,6))
127
  label_count.plot(kind='bar', color='skyblue')
128
+ plt.title("Class Distribution in Prototype Dataset")
129
  plt.xlabel("Class")
130
  plt.ylabel("Count")
131
  plt.tight_layout()
132
 
133
  clearml_logger.report_matplotlib_figure(
134
  title="EDA Class Distribution",
135
+ series="Prototype Subset",
136
  figure=plt.gcf(),
137
  iteration=1
138
  )
 
150
  }
151
 
152
  prototype_loaders = make_dataset_loaders(
153
+ prototyping_dataset, SEED, BATCH_SIZE, TEST_SIZE, aug_config
154
  )
155
 
156
  print("\n--- Handoff Test Successful ---")
 
174
  print(f"Validation loader batches: {len(final_loaders['val'])}")
175
  print(f"Test loader batches: {len(final_loaders['test'])}")
176
 
177
+ # Record dataset info in ClearML
178
+ task.connect_configuration(
179
+ {"dataset_id": clearml_dataset.id},
180
+ name="Dataset Metadata"
181
+ )
182
+ task.mark_completed()
183
 
184
+
185
  # Close the ClearML task
 
186
  task.close()
 
187
  print("\n--- Script Finished ---")
dataPrep/helpers/clearml_data.py CHANGED
@@ -11,7 +11,7 @@ Takes latest Data Prep ClearML task from project and reconstruct:
11
  - data loaders for both full and subset datasets
12
  - Aug settings used
13
  '''
14
- def extract_latest_data_task(project_name: str = "Small Group Project", num_workers: int = 8):
15
 
16
  # --------- Get latest Data Preparation task from ClearML ---------
17
 
 
11
  - data loaders for both full and subset datasets
12
  - Aug settings used
13
  '''
14
+ def extract_latest_data_task(project_name: str = "Small Group Project", num_workers: int = 0):
15
 
16
  # --------- Get latest Data Preparation task from ClearML ---------
17
 
dataPrep/helpers/transforms_loaders.py CHANGED
@@ -103,13 +103,15 @@ def make_dataset_loaders(dataset, seed, batch_size, test_size, aug_config, worke
103
  pin_memory=True,
104
  num_workers=workers
105
  )
 
106
 
107
  print(f"\nWorkers used in DataLoaders: {workers}\n")
108
 
109
  dataset_loaders = {
110
  "train": train_loader,
111
  "val": val_loader,
112
- "test": test_loader
 
113
  }
114
 
115
  return dataset_loaders
 
103
  pin_memory=True,
104
  num_workers=workers
105
  )
106
+ class_names = dataset.features['label'].names
107
 
108
  print(f"\nWorkers used in DataLoaders: {workers}\n")
109
 
110
  dataset_loaders = {
111
  "train": train_loader,
112
  "val": val_loader,
113
+ "test": test_loader,
114
+ "classNames": class_names
115
  }
116
 
117
  return dataset_loaders
models/modelTwo.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class BetterCNN(nn.Module):
6
+ def __init__(self, noOfClasses=39):
7
+ super(BetterCNN, self).__init__()
8
+
9
+ # 32 Channels
10
+ # We use padding=1 to keep spatial size same before pooling
11
+ self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)
12
+ self.bn1 = nn.BatchNorm2d(32)
13
+
14
+ # 64 Channels
15
+ self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
16
+ self.bn2 = nn.BatchNorm2d(64)
17
+
18
+ # 128 Channels
19
+ self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
20
+ self.bn3 = nn.BatchNorm2d(128)
21
+
22
+ # 256 Channels
23
+ self.conv4 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
24
+ self.bn4 = nn.BatchNorm2d(256)
25
+
26
+ # Pooling layer
27
+ self.pool = nn.MaxPool2d(2, 2)
28
+
29
+ # Adaptive Pooling
30
+ self.adaptive_pool = nn.AdaptiveAvgPool2d((4, 4))
31
+
32
+ # Classification Head
33
+ self.fc1 = nn.Linear(256 * 4 * 4, 1024)
34
+ self.dropout = nn.Dropout(0.5) # Dropout after Linear layer
35
+
36
+ self.fc2 = nn.Linear(1024, 512)
37
+ self.fc3 = nn.Linear(512, noOfClasses)
38
+
39
+ def forward(self, x):
40
+ # Block 1
41
+ x = self.conv1(x)
42
+ x = self.bn1(x) # BatchNorm
43
+ x = F.relu(x)
44
+ x = self.pool(x)
45
+
46
+ # Block 2
47
+ x = self.pool(F.relu(self.bn2(self.conv2(x))))
48
+
49
+ # Block 3
50
+ x = self.pool(F.relu(self.bn3(self.conv3(x))))
51
+
52
+ # Block 4
53
+ x = self.pool(F.relu(self.bn4(self.conv4(x))))
54
+
55
+ # Adapt & Flatten
56
+ x = self.adaptive_pool(x)
57
+ x = torch.flatten(x, 1) # Flattens to (Batch, 4096)
58
+
59
+ # Dense Layers
60
+ x = F.relu(self.fc1(x))
61
+ x = self.dropout(x) # Regularization
62
+ x = F.relu(self.fc2(x))
63
+ x = self.fc3(x) # No activation needed here (handled by CrossEntropyLoss)
64
+
65
+ return x
subset_indices.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:972615a5b506b5ee2490f61866c26a4a2f9e2498c0baedb195a2a0d10a62e76f
3
+ size 111016
testingModel/helpers/evaluation.py CHANGED
@@ -1,43 +1,88 @@
1
- import torch
2
- from torch.nn import CrossEntropyLoss
3
-
4
-
5
- """
6
- Evaluates a trained model on a dataloader that returns batches like:
7
- batch["image"] -> Tensor [B, 3, 256, 256]
8
- batch["label"] -> Tensor [B]
9
-
10
- Returns dict:
11
- { "accuracy": float, "loss": float }
12
- """
13
- def make_predictions(model, dataloader, device):
14
-
15
- model.eval()
16
- criterion = CrossEntropyLoss()
17
-
18
- total_loss = 0
19
- total_correct = 0
20
- total_samples = 0
21
-
22
- with torch.no_grad():
23
- for batch in dataloader:
24
-
25
- # Move tensors to device
26
- images = batch["image"].to(device)
27
- labels = batch["label"].to(device).long()
28
-
29
- # Forward pass
30
- outputs = model(images)
31
- loss = criterion(outputs, labels)
32
-
33
- total_loss += loss.item() * images.size(0)
34
- total_correct += (outputs.argmax(dim=1) == labels).sum().item()
35
- total_samples += labels.size(0)
36
-
37
- accuracy = total_correct / total_samples
38
- avg_loss = total_loss / total_samples
39
-
40
- return {
41
- "accuracy": accuracy,
42
- "loss": avg_loss,
43
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch.nn import CrossEntropyLoss
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+
6
+
7
+ """
8
+ Evaluates a trained model on a dataloader that returns batches like:
9
+ batch["image"] -> Tensor [B, 3, 256, 256]
10
+ batch["label"] -> Tensor [B]
11
+ """
12
+ def make_predictions(model, dataloader, device):
13
+
14
+ model.eval()
15
+ criterion = CrossEntropyLoss()
16
+
17
+ total_loss = 0
18
+ total_correct = 0
19
+ total_samples = 0
20
+
21
+ all_preds = []
22
+ all_labels = []
23
+
24
+ with torch.no_grad():
25
+ for batch in dataloader:
26
+
27
+ # Move tensors to device
28
+ images = batch["image"].to(device)
29
+ labels = batch["label"].to(device).long()
30
+
31
+ # Forward pass
32
+ outputs = model(images)
33
+ loss = criterion(outputs, labels)
34
+ preds = outputs.argmax(dim=1)
35
+
36
+ total_loss += loss.item() * images.size(0)
37
+ total_correct += (preds == labels).sum().item()
38
+ total_samples += labels.size(0)
39
+
40
+ # Accumulate all predictions and labels
41
+ all_preds.extend(preds.tolist())
42
+ all_labels.extend(labels.tolist())
43
+
44
+ accuracy = total_correct / total_samples
45
+ avg_loss = total_loss / total_samples
46
+
47
+ return {
48
+ "accuracy": accuracy,
49
+ "loss": avg_loss,
50
+ "predictions": np.array(all_preds),
51
+ "labels": np.array(all_labels),
52
+ }
53
+
54
+
55
+ # Computes per-class accuracies
56
+ def class_accuracies(labels, preds, num_classes):
57
+ correct = np.zeros(num_classes, dtype=int)
58
+ counts = np.zeros(num_classes, dtype=int)
59
+ accuracies = np.zeros(num_classes, dtype=float)
60
+
61
+ for true, pred in zip(labels, preds):
62
+ counts[true] += 1
63
+ if true == pred:
64
+ correct[true] += 1
65
+
66
+ # Calculate accuracies
67
+ for i in range(num_classes):
68
+ if counts[i] > 0:
69
+ accuracies[i] = round(correct[i] / counts[i], 4)
70
+ else:
71
+ accuracies[i] = 0.0
72
+
73
+ return accuracies
74
+
75
+
76
+ def plot_class_accuracies(accuracies, class_names):
77
+ fig, ax = plt.subplots(figsize=(12, 6))
78
+
79
+ ax.set_title("Per-Class Accuracy")
80
+ ax.set_xlabel("Class")
81
+ ax.set_ylabel("Accuracy")
82
+ ax.set_ylim(0, 1.0)
83
+ ax.bar(class_names, accuracies)
84
+
85
+ plt.xticks(rotation=90)
86
+ plt.tight_layout()
87
+
88
+ return fig
testingModel/run_testing.py CHANGED
@@ -1,76 +1,98 @@
1
- from clearml import Task
2
- from dataPrep.helpers.clearml_data import extract_latest_data_task
3
-
4
- import torch
5
- from models.modelOne import modelOne
6
- from testingModel.helpers.evaluation import make_predictions
7
-
8
-
9
- # -------------- Load Data --------------
10
- project_name = "Small Group Project"
11
- subset_loaders, full_loaders, data_prep_metadata = extract_latest_data_task(project_name=project_name)
12
-
13
-
14
- # -------- ClearML Testing Task Setup --------
15
- testing_task = Task.init(
16
- project_name=f"{project_name}/Model Testing",
17
- task_name="Model Testing",
18
- task_type=Task.TaskTypes.testing,
19
- reuse_last_task_id=False,
20
- )
21
-
22
- # Reference the data prep task used
23
- testing_logger = testing_task.get_logger()
24
- testing_task.connect(data_prep_metadata, name="data_prep_metadata_READONLY")
25
-
26
- CLEARML_TRAINING_ID = "5bac154a885b4acbaa07d8588027bb27"
27
-
28
- # Testing parameters - Modify these when experimenting
29
- testing_config = {
30
- "model_train_id": CLEARML_TRAINING_ID,
31
- "num_classes": 39,
32
- "model_path": "best_model.pt",
33
- }
34
- testing_task.connect(testing_config)
35
-
36
- # Load the model weights from ClearML training task
37
- training_task = Task.get_task(task_id=testing_config["model_train_id"])
38
- model_artifact = training_task.artifacts.get("best_model")
39
- model_path = model_artifact.get_local_copy()
40
-
41
- # Reference training metadata
42
- training_hyperparams = training_task.get_parameters_as_dict()
43
- testing_task.connect(training_hyperparams['General'], name="training_metadata_READONLY")
44
-
45
-
46
- # -------- Rebuild the ML model --------
47
- model = modelOne()
48
- state_dict = torch.load(model_path, map_location="cpu") # Load to CPU first
49
- model.load_state_dict(state_dict)
50
- model.eval() # set dropout & batch norm layers to eval mode
51
-
52
- # Move model to GPU if available
53
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
54
- model.to(device)
55
-
56
-
57
- # -------------------- Test model on test set --------------------
58
- testing_logger.report_text("Starting evaluation on TEST SUBSET...\n")
59
- test_subset = subset_loaders['test']
60
-
61
- subset_results = make_predictions(model, test_subset, device)
62
-
63
-
64
- # Accuracy & Loss logging
65
- testing_logger.report_single_value(name="Test Subset Accuracy", value=subset_results["accuracy"])
66
- testing_logger.report_single_value(name="Test Subset Loss", value=subset_results["loss"])
67
-
68
-
69
- # --------- Complete -----------------
70
- print("\n------ Testing Complete ------")
71
- testing_logger.report_text(
72
- f"TEST SUBSET RESULTS:\n"
73
- f"Loss: {subset_results['loss']:.4f}\n"
74
- f"Accuracy: {subset_results['accuracy']:.4f}\n"
75
- )
76
- testing_task.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from clearml import Task
2
+ from dataPrep.helpers.clearml_data import extract_latest_data_task
3
+
4
+ import torch
5
+ from models.modelOne import modelOne
6
+ from models.modelTwo import BetterCNN
7
+ from testingModel.helpers.evaluation import make_predictions, class_accuracies, plot_class_accuracies
8
+
9
+
10
+ # -------------- Load Data --------------
11
+ project_name = "Small Group Project"
12
+ subset_loaders, full_loaders, data_prep_metadata = extract_latest_data_task(project_name=project_name)
13
+
14
+
15
+ # -------- ClearML Testing Task Setup --------
16
+ testing_task = Task.init(
17
+ project_name=f"{project_name}/Model Testing",
18
+ task_name="Model Testing",
19
+ task_type=Task.TaskTypes.testing,
20
+ reuse_last_task_id=False,
21
+ )
22
+
23
+ # Reference the data prep task used
24
+ testing_logger = testing_task.get_logger()
25
+ testing_task.connect(data_prep_metadata, name="data_prep_metadata_READONLY")
26
+
27
+ CLEARML_TRAINING_ID = "dca82d7c2f404c249f2e5325aaf77207"
28
+
29
+ # Testing parameters - Modify these when experimenting
30
+ testing_config = {
31
+ "model_train_id": CLEARML_TRAINING_ID,
32
+ "num_classes": 39,
33
+ "model_path": "best_model.pt",
34
+ }
35
+ testing_task.connect(testing_config)
36
+
37
+ # Load the model weights from ClearML training task
38
+ training_task = Task.get_task(task_id=testing_config["model_train_id"])
39
+ model_artifact = training_task.artifacts.get("best_model")
40
+ model_path = model_artifact.get_local_copy()
41
+
42
+ # Reference training metadata
43
+ training_hyperparams = training_task.get_parameters_as_dict()
44
+ testing_task.connect(training_hyperparams['General'], name="training_metadata_READONLY")
45
+
46
+
47
+ # -------- Rebuild the ML model --------
48
+ model = BetterCNN(noOfClasses=testing_config["num_classes"])
49
+ state_dict = torch.load(model_path, map_location="cpu") # Load to CPU first
50
+ model.load_state_dict(state_dict)
51
+ model.eval() # set dropout & batch norm layers to eval mode
52
+
53
+ # Move model to GPU if available
54
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
55
+ model.to(device)
56
+
57
+
58
+ # -------------------- Test model on test set --------------------
59
+ testing_logger.report_text("Starting evaluation on TEST SUBSET...\n")
60
+ test_subset = subset_loaders['test']
61
+
62
+ subset_results = make_predictions(model, test_subset, device)
63
+
64
+
65
+ # Accuracy & Loss logging
66
+ testing_logger.report_single_value(name="Test Subset Accuracy", value=subset_results["accuracy"])
67
+ testing_logger.report_single_value(name="Test Subset Loss", value=subset_results["loss"])
68
+
69
+ # Compute per-class accuracy
70
+ preds = subset_results["predictions"]
71
+ labels = subset_results["labels"]
72
+ class_acc = class_accuracies(
73
+ labels,
74
+ preds,
75
+ num_classes=testing_config["num_classes"]
76
+ )
77
+
78
+ # Plot with formatted class names
79
+ class_names = subset_loaders['classNames']
80
+ formatted_class_names = [" ".join(name.replace('_', ' ').split()) for name in class_names]
81
+ acc_fig = plot_class_accuracies(class_acc, formatted_class_names)
82
+
83
+ # Log accuracies plot to ClearML
84
+ testing_logger.report_matplotlib_figure(
85
+ title="Subset Per-Class Accuracy",
86
+ series="Class Accuracy",
87
+ figure=acc_fig
88
+ )
89
+
90
+
91
+ # --------- Complete -----------------
92
+ print("\n------ Testing Complete ------")
93
+ testing_logger.report_text(
94
+ f"TEST SUBSET RESULTS:\n"
95
+ f"Loss: {subset_results['loss']:.4f}\n"
96
+ f"Accuracy: {subset_results['accuracy']:.4f}\n"
97
+ )
98
+ testing_task.close()
trainingModel/run_training.py CHANGED
@@ -1,9 +1,9 @@
1
- import os
2
  from clearml import Task
3
  from dataPrep.helpers.clearml_data import extract_latest_data_task
4
 
5
  import torch
6
  from models.modelOne import modelOne
 
7
  from trainingModel.helpers.Training import train_model
8
 
9
 
@@ -37,7 +37,7 @@ training_task.connect(training_config)
37
 
38
 
39
  # -------- Build the ML model --------
40
- model = modelOne(noOfClasses=training_config["num_classes"])
41
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
42
  model.to(device)
43
 
 
 
1
  from clearml import Task
2
  from dataPrep.helpers.clearml_data import extract_latest_data_task
3
 
4
  import torch
5
  from models.modelOne import modelOne
6
+ from models.modelTwo import BetterCNN
7
  from trainingModel.helpers.Training import train_model
8
 
9
 
 
37
 
38
 
39
  # -------- Build the ML model --------
40
+ model = BetterCNN(noOfClasses=training_config["num_classes"])
41
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
42
  model.to(device)
43
 
ui/app.py CHANGED
@@ -23,7 +23,7 @@ from config import *
23
  class PlantDiseaseApp:
24
  def __init__(self):
25
  self.model_loader = ModelLoader()
26
- self.current_modelName = "CNN from Scratch"
27
  self.model = self.model_loader.loadModel(self.current_modelName)
28
  self.flagged_predictions = []
29
  self.class_names = utils.get_class_names()
@@ -48,7 +48,7 @@ class PlantDiseaseApp:
48
  try:
49
  # Load model if needed
50
  if modelName != self.current_modelName:
51
- self.model, self.class_names = self.model_loader.loadModel(modelName)
52
  self.current_modelName = modelName
53
 
54
  # Preprocess image
@@ -61,6 +61,10 @@ class PlantDiseaseApp:
61
  # Convert logits to probabilities
62
  probs = torch.nn.functional.softmax(logits, dim=1).cpu().numpy()[0]
63
 
 
 
 
 
64
  # Map to class names
65
  predictions = {name: float(prob) for name, prob in zip(self.class_names, probs)}
66
 
@@ -155,7 +159,7 @@ def create_interface():
155
  with gr.Row():
156
  model_selector = gr.Dropdown(
157
  choices=list(config.MODEL_CONFIGS.keys()),
158
- value="CNN from Scratch",
159
  label="Select Model",
160
  info="Choose which model to use for predictions"
161
  )
@@ -218,25 +222,6 @@ def create_interface():
218
  outputs=flag_output
219
  )
220
 
221
-
222
- with gr.Tab("Batch Processing"):
223
- gr.Markdown("### Upload multiple images for batch processing")
224
-
225
- batch_input = gr.File(
226
- label="Upload Multiple Images",
227
- file_count="multiple",
228
- type="filepath"
229
- )
230
-
231
- batch_predict_btn = gr.Button("Predict All", variant="primary")
232
-
233
- batch_output = gr.Markdown(label="Batch Results")
234
-
235
- batch_predict_btn.click(
236
- # fn=app.predict_batch,
237
- inputs=[batch_input, model_selector, confidence_slider],
238
- outputs=batch_output
239
- )
240
  with gr.Tab("About"):
241
  gr.Markdown(
242
  """
@@ -258,7 +243,7 @@ def create_interface():
258
  across 39 different plant disease categories.
259
 
260
  ### Model Architecture
261
- - **CNN from Scratch**: Custom convolutional neural network
262
  - **Transfer Learning**: Fine-tuned ResNet18 (if available)
263
 
264
  ### Technology Stack
 
23
  class PlantDiseaseApp:
24
  def __init__(self):
25
  self.model_loader = ModelLoader()
26
+ self.current_modelName = list(config.MODEL_CONFIGS.keys())[0]
27
  self.model = self.model_loader.loadModel(self.current_modelName)
28
  self.flagged_predictions = []
29
  self.class_names = utils.get_class_names()
 
48
  try:
49
  # Load model if needed
50
  if modelName != self.current_modelName:
51
+ self.model = self.model_loader.loadModel(modelName)
52
  self.current_modelName = modelName
53
 
54
  # Preprocess image
 
61
  # Convert logits to probabilities
62
  probs = torch.nn.functional.softmax(logits, dim=1).cpu().numpy()[0]
63
 
64
+
65
+ predID = probs.argmax().item()
66
+ print("predicted index: " + str(predID))
67
+
68
  # Map to class names
69
  predictions = {name: float(prob) for name, prob in zip(self.class_names, probs)}
70
 
 
159
  with gr.Row():
160
  model_selector = gr.Dropdown(
161
  choices=list(config.MODEL_CONFIGS.keys()),
162
+ value="Shallow CNN",
163
  label="Select Model",
164
  info="Choose which model to use for predictions"
165
  )
 
222
  outputs=flag_output
223
  )
224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  with gr.Tab("About"):
226
  gr.Markdown(
227
  """
 
243
  across 39 different plant disease categories.
244
 
245
  ### Model Architecture
246
+ - **Basic CNN**: Custom convolutional neural network
247
  - **Transfer Learning**: Fine-tuned ResNet18 (if available)
248
 
249
  ### Technology Stack
ui/config.py CHANGED
@@ -2,10 +2,14 @@ CLEARML_PROJECT_NAME = "Plant Disease Classifier"
2
  CLEARML_TASK_NAME_DEFAULT = "CNN Training (Latest)"
3
 
4
  MODEL_CONFIGS = {
5
- "CNN from Scratch": {
6
- "description": "Custom CNN model trained from scratch",
7
- "model_type": "cnn",
8
- "clearml_task_id": "01345cf81fba4a2cac1176887bca9407"
9
-
 
 
 
 
10
  }
11
  }
 
2
  CLEARML_TASK_NAME_DEFAULT = "CNN Training (Latest)"
3
 
4
  MODEL_CONFIGS = {
5
+ "intermediate model": {
6
+ "description": "modelTwo trained on 20 epochs",
7
+ "class" : "betterCNN",
8
+ "clearml_task_id": "dca82d7c2f404c249f2e5325aaf77207"
9
+ },
10
+ "advanced model": {
11
+ "description" : "modleTwo trained on 30 epochs",
12
+ "class": "betterCNN",
13
+ "clearml_task_id": "c79a6939b46a4882a7fdaee117b1f32e"
14
  }
15
  }
ui/model_loader.py CHANGED
@@ -2,12 +2,17 @@ import torch
2
  import sys
3
  from pathlib import Path
4
  import config
5
- import utils
6
  from clearml import Task
7
  from models.modelOne import modelOne
 
8
 
9
  sys.path.append(str(Path(__file__).parent.parent))
10
 
 
 
 
 
 
11
  MODEL_ARTIFACT_NAME = 'best_model'
12
 
13
  class ModelLoader:
@@ -22,6 +27,7 @@ class ModelLoader:
22
  raise ValueError(f"ClearML configuration not found for model: {modelName}")
23
 
24
  taskID = modelConfig['clearml_task_id']
 
25
 
26
  try:
27
  print(f"Attempting to fetch '{modelName}' from ClearML task: {taskID}")
@@ -29,16 +35,13 @@ class ModelLoader:
29
  task = Task.get_task(task_id=taskID)
30
  print("Available artifacts:", task.artifacts.keys())
31
 
32
- # Fetch the artifact 'model_one.pt'
33
  artifact = task.artifacts.get(MODEL_ARTIFACT_NAME)
34
-
35
  if artifact is None:
36
  raise RuntimeError(
37
  f"Artifact '{MODEL_ARTIFACT_NAME}' not found in ClearML task {taskID}"
38
  )
39
 
40
  modelPath = artifact.get_local_copy()
41
-
42
  if modelPath is None:
43
  raise RuntimeError(
44
  f"Artifact '{MODEL_ARTIFACT_NAME}' could not be downloaded (returned None)"
@@ -46,8 +49,11 @@ class ModelLoader:
46
 
47
  print(f"Weights downloaded to: {modelPath}")
48
 
49
- # Load PyTorch model
50
- model = modelOne(noOfClasses=39)
 
 
 
51
  stateDict = torch.load(modelPath, map_location=self.device)
52
  model.load_state_dict(stateDict)
53
 
 
2
  import sys
3
  from pathlib import Path
4
  import config
 
5
  from clearml import Task
6
  from models.modelOne import modelOne
7
+ from models.modelTwo import BetterCNN
8
 
9
  sys.path.append(str(Path(__file__).parent.parent))
10
 
11
+ MODEL_CLASSES = {
12
+ "modelOne": modelOne,
13
+ "betterCNN": BetterCNN
14
+ }
15
+
16
  MODEL_ARTIFACT_NAME = 'best_model'
17
 
18
  class ModelLoader:
 
27
  raise ValueError(f"ClearML configuration not found for model: {modelName}")
28
 
29
  taskID = modelConfig['clearml_task_id']
30
+ className = modelConfig['class']
31
 
32
  try:
33
  print(f"Attempting to fetch '{modelName}' from ClearML task: {taskID}")
 
35
  task = Task.get_task(task_id=taskID)
36
  print("Available artifacts:", task.artifacts.keys())
37
 
 
38
  artifact = task.artifacts.get(MODEL_ARTIFACT_NAME)
 
39
  if artifact is None:
40
  raise RuntimeError(
41
  f"Artifact '{MODEL_ARTIFACT_NAME}' not found in ClearML task {taskID}"
42
  )
43
 
44
  modelPath = artifact.get_local_copy()
 
45
  if modelPath is None:
46
  raise RuntimeError(
47
  f"Artifact '{MODEL_ARTIFACT_NAME}' could not be downloaded (returned None)"
 
49
 
50
  print(f"Weights downloaded to: {modelPath}")
51
 
52
+ # Load correct model class
53
+ ModelClass = MODEL_CLASSES[className]
54
+ model = ModelClass(noOfClasses=39)
55
+
56
+ # Load weights
57
  stateDict = torch.load(modelPath, map_location=self.device)
58
  model.load_state_dict(stateDict)
59
 
ui/utils.py CHANGED
@@ -97,14 +97,6 @@ def get_disease_info(class_name):
97
  }
98
 
99
 
100
- def batch_preprocess_images(images):
101
- """
102
- Preprocess a list of images into a batch tensor
103
- """
104
- tensors = [preprocess_image(img) for img in images]
105
- return torch.cat(tensors, dim=0)
106
-
107
-
108
  def create_confidence_label(predictions, top_k=5):
109
  """
110
  Render a formatted multiline prediction list
@@ -120,31 +112,4 @@ def create_confidence_label(predictions, top_k=5):
120
 
121
  def get_class_names():
122
  """Return the loaded class names from the txt file."""
123
- return CLASS_NAMES
124
-
125
- if __name__ == "__main__":
126
- print("Testing utility functions...")
127
-
128
- test_names = [
129
- "Tomato___Late_blight",
130
- "Apple___healthy",
131
- "Corn_(maize)___Common_rust_"
132
- ]
133
-
134
- print("\nClass name formatting:")
135
- for name in test_names:
136
- print(f" {name} -> {format_class_name(name)}")
137
-
138
- print("\nDisease info:")
139
- for name in test_names:
140
- info = get_disease_info(name)
141
- print(f" {name}:")
142
- print(f" Plant: {info['plant']}")
143
- print(f" Disease: {info['disease']}")
144
- print(f" Healthy: {info['is_healthy']}")
145
-
146
- print("\nImage preprocessing:")
147
- dummy_image = Image.new('RGB', (512, 512), color='red')
148
- tensor = preprocess_image(dummy_image)
149
- print(f" Input size: {dummy_image.size}")
150
- print(f" Output tensor shape: {tensor.shape}")
 
97
  }
98
 
99
 
 
 
 
 
 
 
 
 
100
  def create_confidence_label(predictions, top_k=5):
101
  """
102
  Render a formatted multiline prediction list
 
112
 
113
  def get_class_names():
114
  """Return the loaded class names from the txt file."""
115
+ return CLASS_NAMES