MorganBrizon commited on
Commit
ea26380
·
verified ·
1 Parent(s): e9e9fd2

Update prediction.py

Browse files
Files changed (1) hide show
  1. prediction.py +50 -157
prediction.py CHANGED
@@ -3,6 +3,7 @@ import torch
3
  import tensorflow as tf
4
  import pandas as pd
5
  import joblib
 
6
  from sklearn.metrics import confusion_matrix, f1_score, accuracy_score
7
  from preprocessing import preprocess_eeg_file
8
  from preprocessing_2dcnn import convert_epoch_to_spectrogram
@@ -10,252 +11,144 @@ from preprocessing_epilepsynet import *
10
  from EpilepsyNet_model import TimeSeriesAttentionClassifier
11
  from eegnet_model import EEGNet
12
 
13
-
14
-
15
  def aggregate_predictions(spectrogram_list, model, threshold=0.5):
16
-
17
- # Convert each spectrogram to channels-last format.
18
  X = np.array([np.transpose(s, (1, 2, 0)) for s in spectrogram_list])
19
  print(f'---Aggregating predictions from {len(spectrogram_list)} segments---')
20
  preds = model.predict(X)
21
  mean_prob = np.mean(preds[:, 1])
 
22
  final_label = 1 if mean_prob >= threshold else 0
23
- return final_label, mean_prob
24
 
25
  def predict_eeg_recording(edf_path, model_name='2DCNN', threshold=0.5):
26
-
27
-
28
  if model_name == '2DCNN':
29
-
30
  model = tf.keras.models.load_model('model1_2dcnn.h5')
31
  channels = ["EEG FP1-REF", "EEG FP2-REF", "EEG F3-REF", "EEG F4-REF", "EEG C3-REF"]
32
- #Process the edf file
33
- preprocessed_df = preprocess_eeg_file(edf_path, fmin=1.0, fmax=45.0, segment_lenght=5, overlap=2,desired=channels)
34
-
35
  if preprocessed_df is None or preprocessed_df.empty:
36
  raise ValueError("EEG file could not be preprocessed or no valid segments found.")
37
-
38
- # Convert each 5-second segment (each row) into a spectrogram.
39
  spectrogram_list = preprocessed_df.apply(
40
  lambda row: convert_epoch_to_spectrogram(row, channels, fs=250, nperseg=128, noverlap=64), axis=1
41
  ).tolist()
42
-
43
  return aggregate_predictions(spectrogram_list, model, threshold)
44
-
45
  elif model_name == 'EEGNet':
46
  loaded = joblib.load("eegnet_model.joblib")
47
- # Extract the actual state dictionary.
48
  state_dict = loaded["model_state_dict"]
49
-
50
  model = EEGNet(n_channels=21, n_samples=1250, num_classes=2)
51
  model.load_state_dict(state_dict)
52
-
53
- # Define the channels to use for EEGNet
54
  channels = [
55
- 'EEG FP1-REF', # Left frontal pole
56
- 'EEG FP2-REF', # Right frontal pole
57
- 'EEG F3-REF', # Left frontal
58
- 'EEG F4-REF', # Right frontal
59
- 'EEG C3-REF', # Left central
60
- 'EEG C4-REF', # Right central
61
- 'EEG P3-REF', # Left parietal
62
- 'EEG P4-REF', # Right parietal
63
- 'EEG O1-REF', # Left occipital
64
- 'EEG O2-REF', # Right occipital
65
- 'EEG F7-REF', # Left lateral frontal
66
- 'EEG F8-REF', # Right lateral frontal
67
- 'EEG T3-REF', # Left temporal (anterior)
68
- 'EEG T4-REF', # Right temporal (anterior)
69
- 'EEG T5-REF', # Left temporal (posterior)
70
- 'EEG T6-REF', # Right temporal (posterior)
71
- 'EEG FZ-REF', # Frontal midline
72
- 'EEG CZ-REF', # Central midline
73
- 'EEG PZ-REF', # Parietal midline
74
- 'EEG ROC-REF', # Right occipital (often used as reference or an extra site)
75
- 'EEG LOC-REF' # Left occipital (often used as reference or an extra site)
76
- ]
77
- # Process the edf file
78
  preprocessed_df = preprocess_eeg_file(
79
  edf_path, fmin=1.0, fmax=45.0, segment_lenght=5, overlap=0, desired=channels
80
  )
81
-
82
  if preprocessed_df is None or preprocessed_df.empty:
83
  raise ValueError("EEG file could not be preprocessed or no valid segments found.")
84
-
85
- # For EEGNet, we use the raw time series data directly.
86
- # Convert each 5-second segment (row) to a 2D timeseries array of shape (n_channels, n_samples)
87
  timeseries_list = preprocessed_df.apply(
88
  lambda row: convert_epoch_to_timeseries(row, channels), axis=1
89
  ).tolist()
90
-
91
  return aggregate_predictions_EEGNET(timeseries_list, model, threshold)
92
-
93
  elif model_name == 'EpilepsyNet':
94
-
95
- raw = mne.io.read_raw_edf(edf_path,
96
- preload=True,
97
- verbose='ERROR')
98
-
99
- eeg_cols = ['EEG FP1', 'EEG FP2', 'EEG F3', 'EEG F4',
100
- 'EEG C3', 'EEG C4', 'EEG P3', 'EEG P4',
101
- 'EEG O1', 'EEG O2', 'EEG F7', 'EEG F8',
102
- 'EEG T3', 'EEG T4', 'EEG T5', 'EEG T6',
103
- 'EEG T1', 'EEG T2', 'EEG FZ', 'EEG CZ',
104
- 'EEG PZ']
105
 
106
  parameters = {
107
- 'eeg_cols':eeg_cols,
108
- 'segment_duration':60.0, # 60 second segments
109
- 'n_segments_per_file':12, # Split into 12 epochs (5 sec each)
110
- 'samples_per_segment':1250, # 1250 samples per segment (250 Hz sampling rate)
111
- 'random_state':42
112
- }
113
-
114
  X = process_raw_files(
115
  raw_file=raw,
116
  eeg_cols=eeg_cols,
117
  segment_duration=parameters['segment_duration'],
118
  n_segments_per_file=parameters['n_segments_per_file'],
119
  random_state=parameters['random_state']
120
- )
121
 
122
  X_std = standardize_data(X)
123
  corr_matrix = compute_correlation_matrix(X_std)
124
- # print('Correlation matrix shape :',corr_matrix.shape)
125
-
126
  upper_triangle_matrix = extract_upper_triangle(corr_matrix)
127
- # print('Upper Triangle shape :',upper_triangle_matrix.shape)
128
 
129
  X_tensor = torch.tensor(upper_triangle_matrix, dtype=torch.float32)
130
  X_tensor = X_tensor.unsqueeze(0)
131
-
132
- # Model parameters
133
- input_dim = 210 # Size of flattened upper triangle (21*20/2)
134
- embed_dim = 256 # Embedding dimension
135
- num_heads = 16 # Number of attention heads7
136
-
137
  model = TimeSeriesAttentionClassifier(input_dim, embed_dim, num_heads)
138
  model.load_state_dict(torch.load('EpilepsyNet.pth'))
139
  model.eval()
140
- print('¨'*50)
141
- print('Model Prediction :')
142
 
143
  outputs, _ = model(X_tensor)
144
- # For binary classification with sigmoid, prediction is 1 if output > 0.5
145
- predicted = (outputs >= 0.5).float()
146
-
147
- return int(predicted), outputs.float().squeeze().item()
148
 
 
 
149
 
150
  def convert_epoch_to_timeseries(epoch_row, channels):
151
-
152
  ts_list = []
153
  for ch in channels:
154
- # Check if the channel is in the epoch_row; if not, skip it.
155
  if ch in epoch_row:
156
  ts = epoch_row[ch]
157
  ts_list.append(ts)
158
  return np.stack(ts_list, axis=0)
159
 
160
-
161
  def aggregate_predictions_EEGNET(segment_list, model, threshold):
162
- """
163
- Given a list of segments (raw time series data for EEGNet) and a trained
164
- PyTorch model, predict on each segment and then aggregate the predictions.
165
-
166
- For each segment, the model returns a probability vector.
167
- This function averages the predicted probabilities across segments,
168
- and then compares the average probability for class 1 with the provided threshold
169
- to decide the final predicted class.
170
-
171
- Parameters:
172
- segment_list : list of numpy arrays
173
- Each element is a 2D numpy array with shape (n_channels, n_samples)
174
- representing one EEG segment.
175
- model : a trained PyTorch model that accepts input of shape
176
- (batch_size, n_channels, n_samples) and outputs probabilities (or logits) for each class.
177
- threshold : float
178
- The probability threshold to decide class 1.
179
-
180
- Returns:
181
- final_class : int
182
- The aggregated predicted class (0 or 1).
183
- """
184
  model.eval()
185
  preds = []
186
-
187
  with torch.no_grad():
188
  for seg in segment_list:
189
- # Convert the segment to a torch tensor (float32)
190
- # Expected shape: (n_channels, n_samples)
191
  seg_tensor = torch.tensor(seg, dtype=torch.float32)
192
- # Add a batch dimension -> shape: (1, 1, n_channels, n_samples)
193
- seg_tensor = seg_tensor.unsqueeze(0).unsqueeze(0)
194
-
195
- # Forward pass: get the model's output.
196
- # If your model returns logits, you may need to apply softmax.
197
  output = model(seg_tensor)
198
-
199
- # Check if the output is probabilities already or logits.
200
- # For safety, let's apply softmax to ensure we have probabilities.
201
  prob = torch.softmax(output, dim=1)[0].cpu().numpy()
202
-
203
  preds.append(prob)
204
-
205
- # Average the predictions over all segments
206
  avg_pred = np.mean(preds, axis=0)
207
- # For binary classification assume avg_pred[1] is the probability for class 1.
208
  final_class = int(avg_pred[1] >= threshold)
209
 
210
- return final_class, avg_pred[1]
211
-
212
-
213
-
214
- import numpy as np
215
 
216
  def predict_ensemble_eeg_recording(edf_path, ensemble_method, threshold=0.5):
217
- """
218
- The function aggregates the probability scalars from each model and then:
219
- - For soft voting (method="average"): averages the probabilities
220
- - For hard voting (method="voting"): uses majority voting (each model votes 1 if
221
- its probability is >= threshold, else 0)
222
-
223
- Parameters:
224
- edf_path : str
225
- Path to the EEG EDF file.
226
- ensemble_method : str
227
- Aggregation method, either "average" for soft voting or "voting" for hard voting.
228
- threshold : float, default=0.5
229
- The probability threshold to decide class 1.
230
-
231
- Returns:
232
- final_class : int
233
- The final aggregated predicted class (0 or 1).
234
- aggregated : float or list
235
- For "average", the average probability as a float;
236
- for "voting", the list of votes from each model.
237
- """
238
- # Lists to collect probabilities and votes.
239
  pred_prob_list = []
240
  votes = []
241
-
242
- # Iterate over the three model types.
243
  for model_name in ["2DCNN", "EEGNet", "EpilepsyNet"]:
244
- pred_label, prob = predict_eeg_recording(edf_path, model_name=model_name, threshold=threshold)
245
  pred_prob_list.append(prob)
246
  votes.append(int(prob >= threshold))
247
  print(f"Prediction from {model_name}: label={pred_label}, probability={prob}")
248
-
249
  if ensemble_method.lower() == "average":
250
- # Soft voting: average the probabilities.
251
  avg_prob = np.mean(pred_prob_list)
252
  final_class = int(avg_prob >= threshold)
253
  print("Averaged probability:", avg_prob)
254
- return final_class, avg_prob
255
  elif ensemble_method.lower() == "voting":
256
- # Hard voting: majority decision.
257
  final_class = int(round(np.mean(votes)))
258
  print("Votes from each model:", votes)
259
- return final_class, votes
260
  else:
261
  raise ValueError("Ensemble method must be either 'average' or 'voting'.")
 
3
  import tensorflow as tf
4
  import pandas as pd
5
  import joblib
6
+ import mne
7
  from sklearn.metrics import confusion_matrix, f1_score, accuracy_score
8
  from preprocessing import preprocess_eeg_file
9
  from preprocessing_2dcnn import convert_epoch_to_spectrogram
 
11
  from EpilepsyNet_model import TimeSeriesAttentionClassifier
12
  from eegnet_model import EEGNet
13
 
 
 
14
  def aggregate_predictions(spectrogram_list, model, threshold=0.5):
 
 
15
  X = np.array([np.transpose(s, (1, 2, 0)) for s in spectrogram_list])
16
  print(f'---Aggregating predictions from {len(spectrogram_list)} segments---')
17
  preds = model.predict(X)
18
  mean_prob = np.mean(preds[:, 1])
19
+ segment_probs = preds[:, 1].tolist()
20
  final_label = 1 if mean_prob >= threshold else 0
21
+ return final_label, mean_prob, segment_probs
22
 
23
  def predict_eeg_recording(edf_path, model_name='2DCNN', threshold=0.5):
 
 
24
  if model_name == '2DCNN':
 
25
  model = tf.keras.models.load_model('model1_2dcnn.h5')
26
  channels = ["EEG FP1-REF", "EEG FP2-REF", "EEG F3-REF", "EEG F4-REF", "EEG C3-REF"]
27
+ preprocessed_df = preprocess_eeg_file(edf_path, fmin=1.0, fmax=45.0, segment_lenght=5, overlap=2, desired=channels)
28
+
 
29
  if preprocessed_df is None or preprocessed_df.empty:
30
  raise ValueError("EEG file could not be preprocessed or no valid segments found.")
31
+
 
32
  spectrogram_list = preprocessed_df.apply(
33
  lambda row: convert_epoch_to_spectrogram(row, channels, fs=250, nperseg=128, noverlap=64), axis=1
34
  ).tolist()
35
+
36
  return aggregate_predictions(spectrogram_list, model, threshold)
37
+
38
  elif model_name == 'EEGNet':
39
  loaded = joblib.load("eegnet_model.joblib")
 
40
  state_dict = loaded["model_state_dict"]
41
+
42
  model = EEGNet(n_channels=21, n_samples=1250, num_classes=2)
43
  model.load_state_dict(state_dict)
44
+
 
45
  channels = [
46
+ 'EEG FP1-REF', 'EEG FP2-REF', 'EEG F3-REF', 'EEG F4-REF', 'EEG C3-REF', 'EEG C4-REF',
47
+ 'EEG P3-REF', 'EEG P4-REF', 'EEG O1-REF', 'EEG O2-REF', 'EEG F7-REF', 'EEG F8-REF',
48
+ 'EEG T3-REF', 'EEG T4-REF', 'EEG T5-REF', 'EEG T6-REF', 'EEG FZ-REF', 'EEG CZ-REF',
49
+ 'EEG PZ-REF', 'EEG ROC-REF', 'EEG LOC-REF'
50
+ ]
51
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  preprocessed_df = preprocess_eeg_file(
53
  edf_path, fmin=1.0, fmax=45.0, segment_lenght=5, overlap=0, desired=channels
54
  )
55
+
56
  if preprocessed_df is None or preprocessed_df.empty:
57
  raise ValueError("EEG file could not be preprocessed or no valid segments found.")
58
+
 
 
59
  timeseries_list = preprocessed_df.apply(
60
  lambda row: convert_epoch_to_timeseries(row, channels), axis=1
61
  ).tolist()
62
+
63
  return aggregate_predictions_EEGNET(timeseries_list, model, threshold)
64
+
65
  elif model_name == 'EpilepsyNet':
66
+ raw = mne.io.read_raw_edf(edf_path, preload=True, verbose='ERROR')
67
+ eeg_cols = ['EEG FP1', 'EEG FP2', 'EEG F3', 'EEG F4', 'EEG C3', 'EEG C4', 'EEG P3', 'EEG P4',
68
+ 'EEG O1', 'EEG O2', 'EEG F7', 'EEG F8', 'EEG T3', 'EEG T4', 'EEG T5', 'EEG T6',
69
+ 'EEG T1', 'EEG T2', 'EEG FZ', 'EEG CZ', 'EEG PZ']
 
 
 
 
 
 
 
70
 
71
  parameters = {
72
+ 'eeg_cols': eeg_cols,
73
+ 'segment_duration': 60.0,
74
+ 'n_segments_per_file': 12,
75
+ 'samples_per_segment': 1250,
76
+ 'random_state': 42
77
+ }
78
+
79
  X = process_raw_files(
80
  raw_file=raw,
81
  eeg_cols=eeg_cols,
82
  segment_duration=parameters['segment_duration'],
83
  n_segments_per_file=parameters['n_segments_per_file'],
84
  random_state=parameters['random_state']
85
+ )
86
 
87
  X_std = standardize_data(X)
88
  corr_matrix = compute_correlation_matrix(X_std)
 
 
89
  upper_triangle_matrix = extract_upper_triangle(corr_matrix)
 
90
 
91
  X_tensor = torch.tensor(upper_triangle_matrix, dtype=torch.float32)
92
  X_tensor = X_tensor.unsqueeze(0)
93
+
94
+ input_dim = 210
95
+ embed_dim = 256
96
+ num_heads = 16
97
+
 
98
  model = TimeSeriesAttentionClassifier(input_dim, embed_dim, num_heads)
99
  model.load_state_dict(torch.load('EpilepsyNet.pth'))
100
  model.eval()
 
 
101
 
102
  outputs, _ = model(X_tensor)
103
+ predicted = (outputs >= 0.5).float()
 
 
 
104
 
105
+ prob = outputs.float().squeeze().item()
106
+ return int(predicted), prob, [prob]
107
 
108
  def convert_epoch_to_timeseries(epoch_row, channels):
 
109
  ts_list = []
110
  for ch in channels:
 
111
  if ch in epoch_row:
112
  ts = epoch_row[ch]
113
  ts_list.append(ts)
114
  return np.stack(ts_list, axis=0)
115
 
 
116
  def aggregate_predictions_EEGNET(segment_list, model, threshold):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  model.eval()
118
  preds = []
119
+
120
  with torch.no_grad():
121
  for seg in segment_list:
 
 
122
  seg_tensor = torch.tensor(seg, dtype=torch.float32)
123
+ seg_tensor = seg_tensor.unsqueeze(0).unsqueeze(0)
 
 
 
 
124
  output = model(seg_tensor)
 
 
 
125
  prob = torch.softmax(output, dim=1)[0].cpu().numpy()
 
126
  preds.append(prob)
127
+
 
128
  avg_pred = np.mean(preds, axis=0)
129
+ segment_probs = [float(p[1]) for p in preds]
130
  final_class = int(avg_pred[1] >= threshold)
131
 
132
+ return final_class, float(avg_pred[1]), segment_probs
 
 
 
 
133
 
134
  def predict_ensemble_eeg_recording(edf_path, ensemble_method, threshold=0.5):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  pred_prob_list = []
136
  votes = []
137
+
 
138
  for model_name in ["2DCNN", "EEGNet", "EpilepsyNet"]:
139
+ pred_label, prob, _ = predict_eeg_recording(edf_path, model_name=model_name, threshold=threshold)
140
  pred_prob_list.append(prob)
141
  votes.append(int(prob >= threshold))
142
  print(f"Prediction from {model_name}: label={pred_label}, probability={prob}")
143
+
144
  if ensemble_method.lower() == "average":
 
145
  avg_prob = np.mean(pred_prob_list)
146
  final_class = int(avg_prob >= threshold)
147
  print("Averaged probability:", avg_prob)
148
+ return final_class, avg_prob, []
149
  elif ensemble_method.lower() == "voting":
 
150
  final_class = int(round(np.mean(votes)))
151
  print("Votes from each model:", votes)
152
+ return final_class, votes, []
153
  else:
154
  raise ValueError("Ensemble method must be either 'average' or 'voting'.")