MorganBrizon commited on
Commit
ef08a91
·
verified ·
1 Parent(s): 546b54d

Upload 3 files

Browse files
preprocessing_2dcnn.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ from scipy.signal import spectrogram
4
+
5
+ #METHOD 1: STFT spectrogram
6
+ def convert_epoch_to_spectrogram(epoch_row, channels, fs=250, nperseg=128, noverlap=64):
7
+ """
8
+ Given a pandas Series representing one epoch, where each channel column contains
9
+ a 1D numpy array of time series data, compute a spectrogram for each channel and
10
+ stack them into a 3D array of shape (n_channels, freq_bins, time_bins).
11
+
12
+ Parameters:
13
+ epoch_row: pandas Series
14
+ One row of your preprocessed dataframe (one epoch).
15
+ channels: list of str
16
+ The list of channel names to process.
17
+ fs: int
18
+ Sampling frequency (default 250 Hz).
19
+ nperseg: int
20
+ Length of each segment for spectrogram calculation.
21
+ noverlap: int
22
+ Number of overlapping samples between segments.
23
+
24
+ Returns:
25
+ spec_stack: numpy array
26
+ A 3D array with shape (n_channels, freq_bins, time_bins) containing the
27
+ spectrogram (in dB) for each channel.
28
+ """
29
+ spec_list = []
30
+ for ch in channels:
31
+ ts = epoch_row[ch] # this is the 1D time series for the channel
32
+ # Compute the spectrogram
33
+ f, t, Sxx = spectrogram(ts, fs=fs, nperseg=nperseg, noverlap=noverlap)
34
+ # Convert the power spectrogram to dB scale
35
+ Sxx_db = 10 * np.log10(Sxx + 1e-10)
36
+ spec_list.append(Sxx_db)
37
+ spec_stack = np.stack(spec_list, axis=0)
38
+ return spec_stack
39
+
40
+
41
+ def convert_preprocessed_df_to_2d(preprocessed_df, channels=["EEG FP1-REF", "EEG FP2-REF", "EEG F3-REF", "EEG F4-REF", "EEG C3-REF"],
42
+ fs=250, nperseg=128, noverlap=64):
43
+
44
+ # Make a copy to avoid modifying the original dataframe
45
+ df = preprocessed_df.copy()
46
+
47
+ # Compute the spectrogram for each row (epoch)
48
+ df["spectrogram"] = df.apply(lambda row: convert_epoch_to_spectrogram(row, channels, fs, nperseg, noverlap), axis=1)
49
+
50
+ #drop channel columns and other useless ones
51
+ df_final = df[["spectrogram", "epilepsy", "age", "gender",'subject_id', 'edf_path']]
52
+ return df_final
preprocessing_epilepsynet.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mne
2
+ import numpy as np
3
+
4
+ from typing import List, Optional
5
+ import random
6
+
7
+
8
+ def extract_random_segment(raw: mne.io.Raw, duration: float = 60.0,
9
+ random_state: Optional[int] = None) -> mne.io.Raw:
10
+ """
11
+ Extract a random segment of specified duration from a raw MNE file.
12
+
13
+ Parameters:
14
+ -----------
15
+ raw : mne.io.Raw
16
+ The raw MNE object
17
+ duration : float
18
+ Duration of the segment to extract in seconds
19
+ random_state : int, optional
20
+ Random seed for reproducibility
21
+
22
+ Returns:
23
+ --------
24
+ mne.io.Raw
25
+ A cropped raw object containing only the random segment
26
+ """
27
+ if random_state is not None:
28
+ np.random.seed(random_state)
29
+
30
+ # Get the total duration of the raw file
31
+ total_duration = raw.times[-1]
32
+
33
+ # Ensure the raw file is long enough
34
+ if total_duration <= duration:
35
+ raise ValueError(f"Raw file duration ({total_duration:.2f}s) is shorter than requested segment duration ({duration:.2f}s)")
36
+
37
+ # Generate a random start time
38
+ max_start = total_duration - duration
39
+ start_time = np.random.uniform(0, max_start)
40
+ end_time = start_time + duration
41
+
42
+ # Create a copy and crop to the random segment
43
+ raw_segment = raw.copy().crop(tmin=start_time, tmax=end_time)
44
+
45
+ return raw_segment
46
+
47
+
48
+ def segment_to_epochs(raw_segment: mne.io.Raw, n_segments: int = 12) -> mne.Epochs:
49
+ """
50
+ Convert a raw segment into fixed-length epochs.
51
+
52
+ Parameters:
53
+ -----------
54
+ raw_segment : mne.io.Raw
55
+ The raw segment to convert to epochs
56
+ n_segments : int
57
+ Number of segments to create
58
+
59
+ Returns:
60
+ --------
61
+ mne.Epochs
62
+ Epoch object containing the segmented data
63
+ """
64
+ # Calculate duration of each epoch based on total duration and number of segments
65
+ total_duration = raw_segment.times[-1]
66
+ epoch_duration = total_duration / n_segments
67
+
68
+ # Create fixed-length epochs
69
+ epochs = mne.make_fixed_length_epochs(
70
+ raw_segment,
71
+ duration=epoch_duration,
72
+ preload=True,
73
+ reject_by_annotation=True
74
+ )
75
+
76
+ return epochs
77
+
78
+
79
+ def process_raw_files(raw_file: mne.io.Raw,
80
+ eeg_cols: List[str],
81
+ segment_duration: float = 60.0,
82
+ n_segments_per_file: int = 12,
83
+ samples_per_segment: int = 1250,
84
+ random_state: Optional[int] = None) -> np.ndarray:
85
+ """
86
+ Process a list of raw MNE files into a batch of epochs with specific EEG channels.
87
+
88
+ Parameters:
89
+ -----------
90
+ raw_files : mne.io.Raw
91
+ The raw MNE object to make preds on
92
+ eeg_cols : List[str]
93
+ List of EEG channel names to keep
94
+ segment_duration : float
95
+ Duration of random segment to extract from each file in seconds
96
+ n_segments_per_file : int
97
+ Number of segments to create per file
98
+ samples_per_segment : int
99
+ Number of time samples per segment
100
+ random_state : int, optional
101
+ Random seed for reproducibility
102
+
103
+ Returns:
104
+ --------
105
+ np.ndarray
106
+ Array of shape (len(raw_files), n_segments_per_file, len(eeg_cols), samples_per_segment)
107
+ """
108
+ # Initialize the output array
109
+ X = np.zeros((n_segments_per_file, len(eeg_cols), samples_per_segment))
110
+
111
+ # Define duration of each epoch based on number of segment and total duration
112
+ epoch_duration = segment_duration / n_segments_per_file
113
+
114
+
115
+ try:
116
+ # Set different random seed for each file if random_state is provided
117
+ file_random_state = None if random_state is None else random_state
118
+
119
+ # Pick only the specified EEG channels
120
+ available_channels = raw_file.ch_names
121
+ print('Num of availbable ch :', len(available_channels))
122
+ channels_to_use = [ch for ch in available_channels if ch.replace('-REF','').replace('-LE','') in eeg_cols]
123
+ if not channels_to_use:
124
+ raise ValueError(f"None of the specified EEG channels found in file")
125
+
126
+ if len(channels_to_use) < len(eeg_cols):
127
+ print(f"Warning: Only {len(channels_to_use)}/{len(eeg_cols)} EEG channels found in file")
128
+
129
+ # Select only the required channels
130
+ raw_eeg = raw_file.copy().pick_channels(channels_to_use)
131
+
132
+ # Resample to 250Hz
133
+ current_sfreq = int(raw_eeg.info['sfreq'])
134
+ if current_sfreq != 250:
135
+ print(f"🔁 Resample : {current_sfreq} Hz → {250} Hz")
136
+ raw_eeg.resample(250)
137
+
138
+ # Extract random segment
139
+ raw_segment = extract_random_segment(
140
+ raw_eeg,
141
+ duration=segment_duration,
142
+ random_state=file_random_state
143
+ )
144
+
145
+ # Convert to epochs
146
+ epochs = segment_to_epochs(raw_segment, n_segments=n_segments_per_file)
147
+
148
+ # Get the data as array
149
+ epoch_data = epochs.get_data()
150
+
151
+ # Ensure the data has the correct number of time samples
152
+ if epoch_data.shape[2] != samples_per_segment:
153
+ # Resample if necessary
154
+ resampling_freq = samples_per_segment / (epoch_duration / n_segments_per_file)
155
+ raw_segment.resample(resampling_freq)
156
+ epochs = segment_to_epochs(raw_segment, n_segments=n_segments_per_file)
157
+ epoch_data = epochs.get_data()
158
+
159
+ # Store in the output array
160
+ X[:, :len(channels_to_use), :] = epoch_data
161
+
162
+ except Exception as e:
163
+ print(f"Error processing file {str(e)}")
164
+ # Keep zeros in the output array for this file
165
+
166
+ return X
167
+
168
+ # Standardize the data per channel :
169
+ def standardize_data(X: np.ndarray) -> np.ndarray:
170
+ """
171
+ Standardize the data along the last axis (time samples).
172
+
173
+ Parameters:
174
+ -----------
175
+ X : np.ndarray
176
+ Input data of shape (n_samples, n_segments, n_channels, n_time_samples)
177
+
178
+ Returns:
179
+ --------
180
+ np.ndarray
181
+ Standardized data
182
+ """
183
+ # Compute mean and std for each channel across all segments and samples
184
+ mean = np.mean(X, axis=(1), keepdims=True)
185
+ std = np.std(X, axis=(1), keepdims=True)
186
+ print(X.shape)
187
+
188
+ # Standardize the data
189
+ X_standardized = (X - mean) / std
190
+
191
+ return X_standardized
192
+
193
+
194
+ # Compute Correlation Matrix
195
+ def compute_correlation_matrix(X: np.ndarray) -> np.ndarray:
196
+ """
197
+ Compute the correlation matrix for the data.
198
+
199
+ Parameters:
200
+ -----------
201
+ X : np.ndarray
202
+ Input data of shape (n_samples, n_segments, n_channels, n_time_samples)
203
+
204
+ Returns:
205
+ --------
206
+ np.ndarray
207
+ Correlation matrices of shape (n_samples, n_segments, n_channels, n_channels)
208
+ """
209
+ # Declare corr_matrix np array of shape (n_samples, n_segments,n_channels, n_channels)
210
+ corr_matrix = np.zeros((X.shape[0], X.shape[1], X.shape[1]))
211
+ for j in range(X.shape[0]): # for each segment 5 secs
212
+ # Compute the correlation matrix
213
+ temp = np.corrcoef(X[j])
214
+ corr_matrix[j] = np.nan_to_num(temp)
215
+
216
+ return corr_matrix
217
+
218
+
219
+
220
+ # Discard bottom triangle from the matrix:
221
+ def discard_bottom_triangle(matrix):
222
+ """
223
+ Discard the bottom triangle of a square matrix.
224
+
225
+ Parameters:
226
+ - matrix (numpy.ndarray): The input square matrix.
227
+
228
+ Returns:
229
+ - numpy.ndarray: The matrix with the bottom triangle discarded.
230
+ """
231
+ # Create a mask for the upper triangle
232
+ mask = np.triu(np.ones_like(matrix, dtype=bool), k=1)
233
+
234
+ # Apply the mask to the matrix
235
+ upper_triangle = np.where(mask, matrix, 0)
236
+
237
+ return upper_triangle
238
+
239
+ def extract_upper_triangle(corr_matrices):
240
+ """
241
+ Extract upper triangles from correlation matrices
242
+
243
+ Args:
244
+ corr_matrices: numpy array of shape (n_sample, n_segments, n_channels, n_channels)
245
+
246
+ Returns:
247
+ numpy array of shape (n_segments, n_features) where n_features = n_channels*(n_channels-1)/2
248
+ """
249
+ n_segments, n_channels, = corr_matrices.shape[0], corr_matrices.shape[1]
250
+ n_features = n_channels * (n_channels - 1) // 2
251
+
252
+ flattened = np.zeros((n_segments, n_features))
253
+
254
+ for j in range(n_segments):
255
+ # Get upper triangle indices (excluding diagonal)
256
+ upper_indices = np.triu_indices(n_channels, k=1)
257
+ # Extract values
258
+ flattened[j] = corr_matrices[j][upper_indices]
259
+
260
+ return flattened
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ numpy==1.26.4
2
+ pandas==2.2.3
3
+ uvicorn==0.34.0
4
+ scipy==1.15.0
5
+ tensorflow==2.16.2
6
+ scikit-learn==1.6.1
7
+ fastapi==0.115.12
8
+ mne==1.9.0
9
+ python-multipart==0.0.20
10
+ torch==2.2.2
11
+ joblib==1.4.2