Skip to content

Commit 2ac7bb2

Browse files
authored
Unlock the cheat code of the universe (#79)
* Unlock the cheat code of the universe * Fix validation * Remove parens
1 parent d0e5215 commit 2ac7bb2

File tree

10 files changed

+120
-100
lines changed

10 files changed

+120
-100
lines changed

backend/app.py

Lines changed: 2 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,8 @@
1-
from flask import Flask, request
2-
from flask_cors import CORS
31
from waitress import serve
4-
from http import HTTPStatus
52

6-
from backend.request import ClassificationRequest
7-
from backend.response import ClassificationResponse
8-
from backend.spectrogram_generator import SpectrogramGenerator
9-
from classification.parser import get_raw_array
10-
from classification.exceptions import ClassificationError
11-
from classification.config.constants import Sex, ALLOWED_FILE_EXTENSIONS
12-
from classification.model import SleepStagesClassifier
13-
from classification.features.preprocessing import preprocess
3+
from web import App
144

15-
app = Flask(__name__)
16-
sleep_stage_classifier = SleepStagesClassifier()
17-
18-
19-
def allowed_file(filename):
20-
return filename.lower().endswith(ALLOWED_FILE_EXTENSIONS)
21-
22-
23-
@app.route("/")
24-
def status():
25-
return ""
26-
27-
28-
@app.route('/analyze-sleep', methods=['POST'])
29-
def analyze_sleep():
30-
"""
31-
Request payload example
32-
{
33-
"file": File(...),
34-
"device": "CYTON",
35-
"sex": "F",
36-
"age": "23",
37-
"stream_start": 1602895800000,
38-
"bedtime": 1602898320000,
39-
"wakeup": 1602931800000
40-
}
41-
"""
42-
if 'file' not in request.files:
43-
return 'Missing file', HTTPStatus.BAD_REQUEST
44-
file = request.files['file']
45-
46-
if file.filename == '':
47-
return 'No selected file', HTTPStatus.BAD_REQUEST
48-
49-
if not allowed_file(file.filename):
50-
return 'File format not allowed', HTTPStatus.BAD_REQUEST
51-
52-
form_data = request.form.to_dict()
53-
raw_array = get_raw_array(file)
54-
55-
try:
56-
classification_request = ClassificationRequest(
57-
age=int(form_data['age']),
58-
sex=Sex[form_data['sex']],
59-
stream_start=int(form_data['stream_start']),
60-
bedtime=int(form_data['bedtime']),
61-
wakeup=int(form_data['wakeup']),
62-
raw_eeg=raw_array,
63-
)
64-
except (KeyError, ValueError, ClassificationError):
65-
return 'Missing or invalid request parameters', HTTPStatus.BAD_REQUEST
66-
67-
preprocessed_epochs = preprocess(classification_request)
68-
predictions = sleep_stage_classifier.predict(preprocessed_epochs, classification_request)
69-
spectrogram_generator = SpectrogramGenerator(preprocessed_epochs)
70-
classification_response = ClassificationResponse(
71-
classification_request, predictions, spectrogram_generator.generate()
72-
)
73-
74-
return classification_response.response
75-
76-
77-
CORS(app,
78-
resources={r'/*': {"origins": '*'}},
79-
allow_headers='Content-Type')
80-
app.config['CORS_HEADERS'] = 'Content-Type'
5+
app = App()
816

827
if __name__ == '__main__':
838
serve(app, host='0.0.0.0', port=8080)

backend/classification/parser/constants.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,4 @@
88

99
FILE_COLUMN_OFFSET = 1
1010

11-
SESSION_FILE_HEADER_NB_LINES = 4
12-
1311
RETAINED_COLUMNS = tuple(range(FILE_COLUMN_OFFSET, len(EEG_CHANNELS) + 1))

backend/classification/parser/csv.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import pandas as pd
2+
from io import StringIO
23

34
from classification.exceptions import ClassificationError
45

56

6-
def read_csv(file, rows_to_skip=0, columns_to_read=None):
7+
def read_csv(file_content, rows_to_skip=0, columns_to_read=None):
78
try:
8-
raw_array = pd.read_csv(file,
9+
raw_array = pd.read_csv(StringIO(file_content),
910
skiprows=rows_to_skip,
1011
usecols=columns_to_read
1112
).to_numpy()

backend/classification/parser/file_type.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ def __init__(self, parser):
1212
self.parser = parser
1313

1414

15-
def detect_file_type(file) -> FileType:
15+
def detect_file_type(file_content) -> FileType:
1616
"""Detects file type
1717
- file: received as an input file
1818
Returns:
1919
- FileType of the input file
2020
"""
21-
first_line = file.readline().decode("utf-8")
22-
return FileType.SessionFile if "EEG Data" in first_line else FileType.SDFile
21+
return FileType.SessionFile if "EEG Data" in file_content else FileType.SDFile
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,22 @@
11
import re
22

33
from classification.parser.file_type import FileType
4-
from classification.parser.constants import SESSION_FILE_HEADER_NB_LINES
54
from classification.exceptions import ClassificationError
65

76
OPENBCI_CYTON_SD_DEFAULT_SAMPLE_RATE = 250
87

98
SAMPLE_RATE_STRING = "Sample Rate"
10-
SAMPLE_RATE_REGEX = fr"^%{SAMPLE_RATE_STRING} = (\d+)"
9+
SAMPLE_RATE_REGEX = fr"%{SAMPLE_RATE_STRING} = (\d+)"
1110

1211

13-
def detect_sample_rate(file, filetype):
12+
def detect_sample_rate(file_content, filetype):
1413
if filetype == FileType.SDFile:
1514
return OPENBCI_CYTON_SD_DEFAULT_SAMPLE_RATE
1615

17-
for _ in range(SESSION_FILE_HEADER_NB_LINES):
18-
line = file.readline().decode("utf-8")
19-
if SAMPLE_RATE_STRING not in line:
20-
continue
21-
22-
try:
23-
sample_rate_raw = re.search(SAMPLE_RATE_REGEX, line).group(1)
24-
return int(sample_rate_raw)
25-
except BaseException:
26-
raise ClassificationError('Invalid sample rate')
16+
try:
17+
sample_rate_raw = re.search(SAMPLE_RATE_REGEX, file_content).group(1)
18+
return int(sample_rate_raw)
19+
except BaseException:
20+
raise ClassificationError('Invalid sample rate')
2721

2822
raise ClassificationError("Couldn't find sample rate")

backend/requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
Flask==1.1.2
2-
Flask-Cors==1.10.3
1+
falcon==3.0.0a2
32
waitress==1.4.4
43

54
mne==0.21.0

backend/web/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import falcon
2+
3+
from web.ping import Ping
4+
from web.analyze_sleep import AnalyzeSleep
5+
6+
7+
def App():
8+
app = falcon.App(cors_enable=True)
9+
10+
ping = Ping()
11+
app.add_route('/', ping)
12+
13+
analyze = AnalyzeSleep()
14+
app.add_route('/analyze-sleep', analyze)
15+
16+
return app

backend/web/analyze_sleep.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import json
2+
import falcon
3+
4+
from backend.request import ClassificationRequest
5+
from backend.response import ClassificationResponse
6+
from backend.spectrogram_generator import SpectrogramGenerator
7+
from classification.parser import get_raw_array
8+
from classification.exceptions import ClassificationError
9+
from classification.config.constants import Sex, ALLOWED_FILE_EXTENSIONS
10+
from classification.model import SleepStagesClassifier
11+
from classification.features.preprocessing import preprocess
12+
13+
sleep_stage_classifier = SleepStagesClassifier()
14+
15+
16+
class AnalyzeSleep:
17+
@staticmethod
18+
def _validate_file(file_content):
19+
if file_content is None:
20+
raise ClassificationError("Missing file")
21+
22+
@staticmethod
23+
def _validate_filename(filename):
24+
if not filename.lower().endswith(ALLOWED_FILE_EXTENSIONS):
25+
raise ClassificationError('File format not allowed')
26+
27+
@staticmethod
28+
def _parse_form(form):
29+
form_data = {}
30+
file_content = None
31+
32+
for part in form:
33+
if part.name == 'file':
34+
AnalyzeSleep._validate_filename(part.filename)
35+
file_content = part.stream.read().decode('utf-8')
36+
else:
37+
form_data[part.name] = part.text
38+
39+
AnalyzeSleep._validate_file(file_content)
40+
41+
return form_data, file_content
42+
43+
def on_post(self, request, response):
44+
"""
45+
Request payload example
46+
{
47+
"file": File(...),
48+
"device": "CYTON",
49+
"sex": "F",
50+
"age": "23",
51+
"stream_start": 1602895800000,
52+
"bedtime": 1602898320000,
53+
"wakeup": 1602931800000
54+
}
55+
"""
56+
57+
try:
58+
form_data, file = self._parse_form(request.get_media())
59+
raw_array = get_raw_array(file)
60+
classification_request = ClassificationRequest(
61+
age=int(form_data['age']),
62+
sex=Sex[form_data['sex']],
63+
stream_start=int(form_data['stream_start']),
64+
bedtime=int(form_data['bedtime']),
65+
wakeup=int(form_data['wakeup']),
66+
raw_eeg=raw_array,
67+
)
68+
except (KeyError, ValueError, ClassificationError):
69+
response.status = falcon.HTTP_400
70+
response.content_type = falcon.MEDIA_TEXT
71+
response.body = 'Missing or invalid request parameters'
72+
return
73+
74+
preprocessed_epochs = preprocess(classification_request)
75+
predictions = sleep_stage_classifier.predict(preprocessed_epochs, classification_request)
76+
spectrogram_generator = SpectrogramGenerator(preprocessed_epochs)
77+
classification_response = ClassificationResponse(
78+
classification_request, predictions, spectrogram_generator.generate()
79+
)
80+
81+
response.body = json.dumps(classification_response.response)

backend/web/ping.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import falcon
2+
3+
4+
class Ping:
5+
def on_get(self, request, response):
6+
response.content_type = falcon.MEDIA_TEXT
7+
response.body = ''

web/src/views/analyze_sleep/upload_form/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ const UploadForm = () => {
166166
// prettier-ignore
167167
const streamStart = new Date(`${getValues('stream_start_date')} ${getValues('stream_start_time')}`);
168168
const bedTime = new Date(`${getValues('bedtime_date')} ${getValues('bedtime_time')}`);
169-
if (streamStart > bedTime) {
169+
if (streamStart >= bedTime) {
170170
return 'Stream start must be prior to bedtime.';
171171
}
172172
},

0 commit comments

Comments
 (0)