-
Notifications
You must be signed in to change notification settings - Fork 3
[WIP] Backend/metrics #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
028571b
added board type & completed metadata response
conorato 2a40453
added base work for tests
conorato a63d07d
setup tests
conorato a4465a6
Merge branch 'master' of github.com:PolyCortex/polydodo into backend/…
conorato 6fab7a9
moved server files not related to classification to backend package
conorato f52b5c1
added time passed in stage metrics
conorato 070e99a
added time passed in each sleep stages tests
conorato 3babe48
tests pass for time passed in stage
conorato d4a7808
added latency tests
conorato 7db5f06
latency tests passes
conorato 2402eb0
added tests to backend CI
conorato e40a02b
added onset tests
conorato 2f6363b
completed onsets
conorato c7811fa
added sleep offset
conorato 9e9f704
added wake after sleep offset
conorato 36076f8
added efficient tests
conorato c0ec7c2
added efficiency
8000
conorato abb8b69
Merge branch 'master' of github.com:PolyCortex/polydodo into backend/…
conorato 8af967b
fixed warning
conorato e9ca36e
added tests
conorato f06294b
moved all metrics to single module
conorato 0aa377e
refactored and added stage shifts
conorato 67ebf3e
fixed awakenings tests
conorato e8b4767
return -1 timestamps in case user doesnt sleep
conorato 9c9e589
refactored metrics for the 2nd time ......
conorato 8eebed8
fixed server errors
conorato c5787fc
Merge branch 'master' of github.com:PolyCortex/polydodo into backend/…
conorato 691b31e
removed acquisition board (now we autodetect) && added tests instruct…
conorato 5d65143
extracted stream duration
conorato 9101fa3
fixed tests
conorato 10e74e2
code review
conorato 26bef35
code review
conorato 6d421fe
Merge branch 'master' into backend/metrics
abelfodil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
from collections import Counter | ||
import numpy as np | ||
|
||
from classification.config.constants import SleepStage, EPOCH_DURATION | ||
|
||
|
||
class Metrics(): | ||
def __init__(self, sleep_stages, bedtime): | ||
self.sleep_stages = sleep_stages | ||
self.bedtime = bedtime | ||
self.has_slept = len(np.unique(self.sleep_stages)) != 1 or np.unique(self.sleep_stages)[0] != SleepStage.W.name | ||
|
||
self.is_sleeping_stages = self.sleep_stages != SleepStage.W.name | ||
self.sleep_indexes = np.where(self.is_sleeping_stages)[0] | ||
self.is_last_stage_sleep = self.sleep_stages[-1] != SleepStage.W.name | ||
|
||
self._initialize_sleep_offset() | ||
self._initialize_sleep_latency() | ||
self._initialize_rem_latency() | ||
self._initialize_transition_based_metrics() | ||
|
||
@property | ||
def report(self): | ||
report = { | ||
'sleepOffset': self._sleep_offset, | ||
'sleepLatency': self._sleep_latency, | ||
'remLatency': self._rem_latency, | ||
'awakenings': self._awakenings, | ||
'stageShifts': self._stage_shifts, | ||
'sleepTime': self._sleep_time, | ||
'WASO': self._wake_after_sleep_onset, | ||
'sleepEfficiency': self._sleep_efficiency, | ||
'efficientSleepTime': self._efficient_sleep_time, | ||
'wakeAfterSleepOffset': self._wake_after_sleep_offset, | ||
'sleepOnset': self._sleep_onset, | ||
'remOnset': self._rem_onset, | ||
**self._time_passed_in_stage, | ||
} | ||
|
||
for metric in report: | ||
# json does not recognize NumPy data types | ||
if isinstance(report[metric], np.int64): | ||
report[metric] = int(report[metric]) | ||
abelfodil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return report | ||
|
||
@property | ||
def _sleep_time(self): | ||
if not self.has_slept: | ||
return 0 | ||
|
||
return self._sleep_offset - self._sleep_onset | ||
|
||
@property | ||
def _wake_after_sleep_onset(self): | ||
if not self.has_slept: | ||
return 0 | ||
|
||
return self._sleep_time - self._efficient_sleep_time | ||
|
||
@property | ||
def _time_passed_in_stage(self): | ||
"""Calculates time passed in each stage for all of the sequence""" | ||
nb_epoch_passed_by_stage = Counter(self.sleep_stages) | ||
|
||
def get_time_passed(stage): | ||
return EPOCH_DURATION * nb_epoch_passed_by_stage[stage] if stage in nb_epoch_passed_by_stage else 0 | ||
|
||
return { | ||
f"{stage.upper()}Time": get_time_passed(stage) | ||
for stage in SleepStage.tolist() | ||
} | ||
|
||
@property | ||
def _sleep_efficiency(self): | ||
return len(self.sleep_indexes) / len(self.sleep_stages) | ||
|
||
@property | ||
def _efficient_sleep_time(self): | ||
return len(self.sleep_indexes) * EPOCH_DURATION | ||
|
||
@property | ||
def _wake_after_sleep_offset(self): | ||
if not self.has_slept: | ||
return 0 | ||
|
||
wake_after_sleep_offset_nb_epochs = ( | ||
len(self.sleep_stages) - self.sleep_indexes[-1] - 1 | ||
) if not self.is_last_stage_sleep else 0 | ||
|
||
return wake_after_sleep_offset_nb_epochs * EPOCH_DURATION | ||
|
||
@property | ||
def _sleep_onset(self): | ||
if not self.has_slept: | ||
return None | ||
|
||
return self._sleep_latency + self.bedtime | ||
|
||
@property | ||
def _rem_onset(self): | ||
rem_latency = self._rem_latency | ||
if rem_latency is None: | ||
return None | ||
|
||
return rem_latency + self.bedtime | ||
|
||
def _initialize_sleep_offset(self): | ||
if not self.has_slept: | ||
sleep_offset = None | ||
else: | ||
sleep_nb_epochs = (self.sleep_indexes[-1] + 1) if len(self.sleep_indexes) else len(self.sleep_stages) | ||
sleep_offset = sleep_nb_epochs * EPOCH_DURATION + self.bedtime | ||
|
||
self._sleep_offset = sleep_offset | ||
|
||
def _initialize_sleep_latency(self): | ||
self._sleep_latency = self._get_latency_of_stage(self.is_sleeping_stages) | ||
|
||
def _initialize_rem_latency(self): | ||
"""Time it took to enter REM stage""" | ||
self._rem_latency = self._get_latency_of_stage(self.sleep_stages == SleepStage.REM.name) | ||
|
||
def _initialize_transition_based_metrics(self): | ||
consecutive_stages_occurences = Counter(zip(self.sleep_stages[:-1], self.sleep_stages[1:])) | ||
occurences_by_transition = { | ||
consecutive_stages: consecutive_stages_occurences[consecutive_stages] | ||
for consecutive_stages in consecutive_stages_occurences if consecutive_stages[0] != consecutive_stages[1] | ||
} | ||
transition_occurences = list(occurences_by_transition.values()) | ||
awakenings_occurences = [ | ||
occurences_by_transition[transition_stages] | ||
for transition_stages in occurences_by_transition | ||
if transition_stages[0] != SleepStage.W.name | ||
and transition_stages[1] == SleepStage.W.name | ||
] | ||
nb_stage_shifts = sum(transition_occurences) | ||
nb_awakenings = sum(awakenings_occurences) | ||
|
||
if self.is_last_stage_sleep and self.has_slept: | ||
nb_stage_shifts += 1 | ||
nb_awakenings += 1 | ||
|
||
self._stage_shifts = nb_stage_shifts | ||
self._awakenings = nb_awakenings | ||
|
||
def _get_latency_of_stage(self, sequence_is_stage): | ||
epochs_of_stage_of_interest = np.where(sequence_is_stage)[0] | ||
|
||
if len(epochs_of_stage_of_interest) == 0: | ||
return None | ||
|
||
return epochs_of_stage_of_interest[0] * EPOCH_DURATION |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
hupper==1.10.2 | ||
pyinstaller==4.0 | ||
pytest==6.1.2 | ||
snakeviz==2.1.0 | ||
Werkzeug==1.0.1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from unittest.mock import patch | ||
|
||
from backend.request import ClassificationRequest | ||
from classification.config.constants import Sex | ||
|
||
|
||
def pytest_generate_tests(metafunc): | ||
# called once per each test function | ||
funcarglist = metafunc.cls.params[metafunc.function.__name__] | ||
argnames = sorted(funcarglist[0]) | ||
metafunc.parametrize( | ||
argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist] | ||
) | ||
|
||
|
||
def get_mock_request(): | ||
with patch.object(ClassificationRequest, '_validate', lambda *x, **y: None): | ||
mock_request = ClassificationRequest( | ||
sex=Sex.M, | ||
age=22, | ||
stream_start=1582418280, | ||
bedtime=1582423980, | ||
wakeup=1582452240, | ||
raw_eeg=None, | ||
stream_duration=35760, | ||
) | ||
|
||
return mock_request |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.