MorganBrizon commited on
Commit
0272714
·
verified ·
1 Parent(s): bb08eff

Upload 11 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM continuumio/miniconda3
2
+
3
+ # Update apt-get and install basic utilities
4
+ RUN apt-get update -y && apt-get install -y nano unzip curl
5
+
6
+ # THIS IS SPECIFIC TO HUGGINGFACE: Create a non-root user
7
+ RUN useradd -m -u 1000 user
8
+
9
+ USER user
10
+ ENV HOME=/home/user
11
+ ENV PATH=/home/user/.local/bin:$PATH
12
+
13
+ # Set working directory
14
+ WORKDIR $HOME/app
15
+
16
+ # Copy only requirements file first to leverage Docker caching
17
+ COPY --chown=user requirements.txt /dependencies/requirements.txt
18
+
19
+ # Install Python dependencies from requirements.txt
20
+ RUN pip install --no-cache-dir -r /dependencies/requirements.txt
21
+
22
+ # Copy the rest of the application code
23
+ COPY --chown=user . $HOME/app
24
+
25
+ EXPOSE 7860
26
+
27
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
__pycache__/app.cpython-312.pyc ADDED
Binary file (3.12 kB). View file
 
__pycache__/prediction.cpython-312.pyc ADDED
Binary file (2.24 kB). View file
 
__pycache__/preprocessing.cpython-312.pyc ADDED
Binary file (4.15 kB). View file
 
__pycache__/preprocessing_2dcnn.cpython-312.pyc ADDED
Binary file (2.49 kB). View file
 
app.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import uvicorn
4
+ from fastapi import FastAPI, UploadFile, File, HTTPException
5
+ from fastapi.responses import JSONResponse
6
+ import tensorflow as tf
7
+
8
+ # Import your prediction function from your prediction module.
9
+ from prediction import predict_eeg_recording
10
+
11
+ app = FastAPI(title="EEG Epilepsy Prediction API")
12
+
13
+ # Load the trained model once at startup.
14
+ model = tf.keras.models.load_model('model1_2dcnn.h5')
15
+
16
+ @app.get("/", tags=["Introduction Endpoints"])
17
+ async def index():
18
+ """
19
+ Simply returns a welcome message!
20
+ """
21
+ message = (
22
+ "Hello world! Welcome to the EEG Epilepsy Prediction API. "
23
+ "Submit an EEG recording EDF file to the `/predict` endpoint to receive a prediction."
24
+ )
25
+ return message
26
+
27
+ @app.post("/predict", tags=["Machine Learning"])
28
+ async def predict_endpoint(file: UploadFile = File(...)):
29
+ """
30
+ Accepts an EEG EDF file, processes it using the preprocessing and spectrogram conversion functions,
31
+ and returns an aggregated prediction (epilepsy or no epilepsy) along with the mean probability.
32
+ """
33
+ # Save the uploaded file temporarily.
34
+ try:
35
+ suffix = os.path.splitext(file.filename)[1]
36
+ with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
37
+ tmp.write(await file.read())
38
+ tmp_path = tmp.name
39
+ except Exception as e:
40
+ raise HTTPException(status_code=500, detail="Error saving temporary file")
41
+
42
+ # Use your prediction function to process the file and obtain an aggregated prediction.
43
+ try:
44
+ pred_label, mean_prob = predict_eeg_recording(tmp_path, model, threshold=0.5)
45
+ except Exception as e:
46
+ os.remove(tmp_path)
47
+ raise HTTPException(status_code=400, detail=f"Prediction failed: {e}")
48
+
49
+ os.remove(tmp_path)
50
+
51
+ response = {
52
+ "prediction": "epilepsy" if pred_label == 1 else "no epilepsy",
53
+ "mean_probability": float(mean_prob)
54
+ }
55
+ return JSONResponse(content=response)
56
+
57
+ if __name__ == "__main__":
58
+ uvicorn.run(app, host="0.0.0.0", port=7860)
model1_2dcnn.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7667171590e5ee5d9a189a22f454b08ec8d5dfcdfd42b9dda4c96ea0f32c07f3
3
+ size 1679000
prediction.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import tensorflow as tf
3
+ import pandas as pd
4
+ from sklearn.metrics import confusion_matrix, f1_score, accuracy_score
5
+ from preprocessing import preprocess_eeg_file
6
+ from preprocessing_2dcnn import convert_epoch_to_spectrogram
7
+ import random
8
+ random.seed(42)
9
+
10
+ def aggregate_predictions(spectrogram_list, model, threshold=0.5):
11
+
12
+ # Convert each spectrogram to channels-last format.
13
+ X = np.array([np.transpose(s, (1, 2, 0)) for s in spectrogram_list])
14
+ print(f'---Aggregating predictions from {len(spectrogram_list)} segments---')
15
+ preds = model.predict(X)
16
+ mean_prob = np.mean(preds[:, 1])
17
+ final_label = 1 if mean_prob >= threshold else 0
18
+ return final_label, mean_prob
19
+
20
+ def predict_eeg_recording(edf_path, model, threshold=0.5):
21
+ #Process the edf file
22
+ preprocessed_df = preprocess_eeg_file(edf_path, fmin=1.0, fmax=45.0, segment_lenght=5, overlap=2)
23
+
24
+ if preprocessed_df is None or preprocessed_df.empty:
25
+ raise ValueError("EEG file could not be preprocessed or no valid segments found.")
26
+
27
+ channels = ["EEG FP1-REF", "EEG FP2-REF", "EEG F3-REF", "EEG F4-REF", "EEG C3-REF"]
28
+ # Convert each 5-second segment (each row) into a spectrogram.
29
+ spectrogram_list = preprocessed_df.apply(
30
+ lambda row: convert_epoch_to_spectrogram(row, channels, fs=250, nperseg=128, noverlap=64), axis=1
31
+ ).tolist()
32
+
33
+ return aggregate_predictions(spectrogram_list, model, threshold)
34
+
preprocessing.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import mne
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+
7
+ def standardize_dataframe(df):
8
+ # Make a copy to avoid modifying the original dataframe
9
+ df_standardized = df.copy()
10
+
11
+ # Only standardize numeric columns
12
+ numeric_columns = df.select_dtypes(include=np.number).columns
13
+
14
+ for column in numeric_columns:
15
+ mean = df[column].mean()
16
+ std = df[column].std()
17
+
18
+ df_standardized[column] = (df[column] - mean) / std
19
+
20
+ return df_standardized
21
+
22
+ def select_relevant_channels(raw):
23
+ # For relevant channel criteria check documentation
24
+ '''“EEG FP1-REF” for the left frontal pole
25
+
26
+ “EEG FP2-REF” for the right frontal pole
27
+
28
+ “EEG F3-REF” for the left frontal region
29
+
30
+ “EEG F4-REF” for the right frontal region
31
+
32
+ “EEG C3-REF” for the left central region'''
33
+
34
+ desired = ["EEG FP1-REF", "EEG FP2-REF", "EEG F3-REF", "EEG F4-REF", "EEG C3-REF"]
35
+ #check if all desired channels are present; if not, skip this file
36
+ if not all(ch in raw.ch_names for ch in desired):
37
+ print("Skipping file because it doesn't have the full set of desired channels.")
38
+ return None
39
+ raw.pick_channels(desired, verbose=False)
40
+ return raw
41
+
42
+
43
+ def collapse_epoch_df_by_channel(epoch_df):
44
+ # Identify channel columns (exclude time, epoch, condition)
45
+ channel_cols = [col for col in epoch_df.columns if col not in ['time', 'epoch']]
46
+ # Group by epoch
47
+ grouped = epoch_df.groupby('epoch')
48
+ rows = []
49
+ for epoch_num, group in grouped:
50
+ group_sorted = group.sort_values('time')
51
+ # For each channel, extract the 1D array for this epoch
52
+ row = {'epoch': epoch_num}
53
+ for ch in channel_cols:
54
+ row[ch] = group_sorted[ch].values # 1D array of length = number of time samples in the epoch
55
+ rows.append(row)
56
+ return pd.DataFrame(rows)
57
+
58
+ def preprocess_eeg_file(edf_path, fmin=1.0, fmax=45.0, segment_lenght=5, overlap=2):
59
+
60
+ # 1. Charger le fichier EDF avec MNE
61
+ raw = mne.io.read_raw_edf(edf_path, preload=True, verbose=False)
62
+
63
+ # Resample (to 250 because it's the lowest sampling rate )
64
+ raw.resample(250, verbose=False)
65
+
66
+ # Filtrage passe-bande (1-45 Hz)
67
+ raw.filter(fmin, fmax, fir_design='firwin', verbose=False)
68
+
69
+ # Skip EEGs less than 5s
70
+ if raw.times[-1] < 5:
71
+ print(f"Skipping {edf_path}: duration ({raw.times[-1]:.2f} s) is less than required 5s.")
72
+ return None
73
+
74
+ # Suppression des canaux non EEG
75
+ eeg_channels = mne.pick_types(raw.info, eeg=True, exclude=[])
76
+ raw.pick(eeg_channels, verbose=False)
77
+
78
+ # Selectionner les channels pertinents (channel selection from EDA ?)
79
+ print(raw.ch_names)
80
+ raw = select_relevant_channels(raw)
81
+ if raw is None:
82
+ return None
83
+
84
+ # Segmentation
85
+ epochs = mne.make_fixed_length_epochs(raw, duration=segment_lenght, preload=False, overlap=overlap, verbose=False)
86
+
87
+ # Transform to dataframe and standadize
88
+
89
+ df = epochs.to_data_frame() # epochs is returned by preprocess_eeg_file()
90
+ df_std = standardize_dataframe(df.drop(['time','epoch', 'condition'], axis=1))
91
+ result = pd.concat([df[['time','epoch']], df_std], axis=1)
92
+
93
+ return collapse_epoch_df_by_channel(result)
94
+
95
+
96
+
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
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
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
+