Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions backend/classification/config/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ def get_filename(self):
FILE_MINIMUM_DURATION = EPOCH_DURATION

DATASET_SAMPLE_RATE = 100
OPENBCI_CYTON_SAMPLE_RATE = 250
OPENBCI_GANGLION_SAMPLE_RATE = 200

AGE_FEATURE_BINS = [
[12, 49],
Expand Down
21 changes: 12 additions & 9 deletions backend/classification/parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
"""
from mne import create_info
from mne.io import RawArray
import numpy as np

from classification.config.constants import OPENBCI_CYTON_SAMPLE_RATE, EEG_CHANNELS
from classification.parser.constants import SCALE_V_PER_COUNT
from classification.parser.file_type import FileType, detect_file_type
from classification.config.constants import EEG_CHANNELS
from classification.parser.file_type import detect_file_type
from classification.parser.sample_rate import detect_sample_rate


def get_raw_array(file):
Expand All @@ -32,14 +31,18 @@ def get_raw_array(file):
Detected {filetype.name} format.
""")

parse = filetype.parser
eeg_raw = parse(file)
sample_rate = detect_sample_rate(file, filetype)
print(f"""
Detected {sample_rate}Hz sample rate.
""")

eeg_raw = filetype.parser(file)

raw_object = RawArray(
SCALE_V_PER_COUNT * np.transpose(eeg_raw),
eeg_raw,
info=create_info(
ch_names=EEG_CHANNELS,
sfreq=OPENBCI_CYTON_SAMPLE_RATE,
sfreq=sample_rate,
ch_types='eeg'),
verbose=False,
)
Expand All @@ -48,7 +51,7 @@ def get_raw_array(file):
First sample values: {raw_object[:, 0]}
Second sample values: {raw_object[:, 1]}
Number of samples: {raw_object.n_times}
Duration of signal (h): {raw_object.n_times / (3600 * OPENBCI_CYTON_SAMPLE_RATE)}
Duration of signal (h): {raw_object.n_times / (3600 * sample_rate)}
Channel names: {raw_object.ch_names}
""")

Expand Down
5 changes: 4 additions & 1 deletion backend/classification/parser/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
ADS1299_Vref = 4.5
ADS1299_gain = 24.
SCALE_uV_PER_COUNT = ADS1299_Vref / ((2**23) - 1) / ADS1299_gain * 1000000
SCALE_V_PER_COUNT = SCALE_uV_PER_COUNT / 1e6
uV_TO_V = 1 / 1e6
SCALE_V_PER_COUNT = SCALE_uV_PER_COUNT * uV_TO_V

FILE_COLUMN_OFFSET = 1

SESSION_FILE_HEADER_NB_LINES = 4

RETAINED_COLUMNS = tuple(range(FILE_COLUMN_OFFSET, len(EEG_CHANNELS) + 1))
7 changes: 4 additions & 3 deletions backend/classification/parser/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

from classification.exceptions import ClassificationError


def read_csv(file, rows_to_skip=0, columns_to_read=None):
try:
raw_array = pd.read_csv(file,
skiprows=rows_to_skip,
usecols=columns_to_read
).to_numpy()
skiprows=rows_to_skip,
usecols=columns_to_read
).to_numpy()
except Exception:
raise ClassificationError()

Expand Down
10 changes: 6 additions & 4 deletions backend/classification/parser/file_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from classification.parser.sd_file import parse_sd_file
from classification.parser.session_file import parse_session_file


class FileType(Enum):
SDFile = (parse_sd_file,)
SessionFile = (parse_session_file,)
def __init__(self, parser):
self.parser = parser
SDFile = (parse_sd_file,)
SessionFile = (parse_session_file,)

def __init__(self, parser):
self.parser = parser


def detect_file_type(file) -> FileType:
Expand Down
28 changes: 28 additions & 0 deletions backend/classification/parser/sample_rate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import re

from classification.parser.file_type import FileType
from classification.parser.constants import SESSION_FILE_HEADER_NB_LINES
from classification.exceptions import ClassificationError

OPENBCI_CYTON_SD_DEFAULT_SAMPLE_RATE = 250

SAMPLE_RATE_STRING = "Sample Rate"
SAMPLE_RATE_REGEX = fr"^%{SAMPLE_RATE_STRING} = (\d+)"


def detect_sample_rate(file, filetype):
if filetype == FileType.SDFile:
return OPENBCI_CYTON_SD_DEFAULT_SAMPLE_RATE

for _ in range(SESSION_FILE_HEADER_NB_LINES):
line = file.readline().decode("utf-8")
if SAMPLE_RATE_STRING not in line:
continue

try:
sample_rate_raw = re.search(SAMPLE_RATE_REGEX, line).group(1)
return int(sample_rate_raw)
Comment thread
abelfodil marked this conversation as resolved.
except BaseException:
raise ClassificationError('Invalid sample rate')

raise ClassificationError("Couldn't find sample rate")
3 changes: 2 additions & 1 deletion backend/classification/parser/sd_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from classification.parser.constants import RETAINED_COLUMNS
from classification.parser.csv import read_csv
from classification.parser.constants import SCALE_V_PER_COUNT

ROWS_TO_SKIP = 2

Expand All @@ -26,5 +27,5 @@ def parse_sd_file(file):
eeg_raw = read_csv(file, ROWS_TO_SKIP, RETAINED_COLUMNS)
hexstr_to_int = np.vectorize(_hexstr_to_int)
eeg_raw = hexstr_to_int(eeg_raw)

eeg_raw = np.transpose(eeg_raw) * SCALE_V_PER_COUNT
return eeg_raw
8 changes: 5 additions & 3 deletions backend/classification/parser/session_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pandas as pd
import numpy as np

from classification.parser.constants import RETAINED_COLUMNS
from classification.parser.constants import RETAINED_COLUMNS, uV_TO_V
from classification.parser.csv import read_csv


Expand All @@ -14,4 +14,6 @@ def parse_session_file(file):
Returns:
- np.array of the two EEG channels of interest
"""
return read_csv(file, ROWS_TO_SKIP, RETAINED_COLUMNS)
eeg_raw = read_csv(file, ROWS_TO_SKIP, RETAINED_COLUMNS)
eeg_raw = np.transpose(eeg_raw) * uV_TO_V
return eeg_raw
4 changes: 0 additions & 4 deletions web/src/views/analyze_sleep/upload_form/constants.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
export const MAX_FILE_UPLOAD_SIZE = 1.6e9;
export const ACCEPTED_FILE_TYPE = 'text/plain';
export const ACCEPTED_FILE_EXTENSION = '.txt';
export const DEVICES = {
CYTON: 'CYTON',
GANGLION: 'GANGLION',
};
export const MIN_AGE = 12;
export const MAX_AGE = 125;
27 changes: 1 addition & 26 deletions web/src/views/analyze_sleep/upload_form/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@ import { useForm } from 'react-hook-form';
import { Button, Container, CustomInput, Form, FormGroup, Label, Input, InputGroup, Col, Row, Alert } from 'reactstrap';
import { DateTime } from 'luxon';

import {
ACCEPTED_FILE_EXTENSION,
ACCEPTED_FILE_TYPE,
DEVICES,
MAX_AGE,
MAX_FILE_UPLOAD_SIZE,
MIN_AGE,
} from './constants';
import { ACCEPTED_FILE_EXTENSION, ACCEPTED_FILE_TYPE, MAX_AGE, MAX_FILE_UPLOAD_SIZE, MIN_AGE } from './constants';

import './style.css';
import useGlobalState from 'hooks/useGlobalState';
Expand Down Expand Up @@ -111,24 +104,6 @@ const UploadForm = () => {
While recording your EEG data, you also gathered some informations about your device, yourself and your
sleep:
</p>
<FormGroup>
<Label for="openbci-device">OpenBCI device used</Label>
<Row>
<Col md={12}>
<CustomInput
innerRef={register({ required: 'A selection must be made.' })}
type="select"
id="openbci-device"
name="device"
>
<option defaultValue></option>
<option value={DEVICES.CYTON}>Cyton</option>
<option value={DEVICES.GANGLION}>Ganglion</option>
</CustomInput>
<div className="upload-form__error-text">{errors.device?.message}</div>
</Col>
</Row>
</FormGroup>

<Row form>
<Col md={6}>
Expand Down