Skip to content
Open
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
3 changes: 3 additions & 0 deletions AlaskaEx1.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
file_path_raw_signals,file_path_preprocessed_signals,file_path_manual_state_annot_orig,file_path_manual_state_annotation,file_path_automated_state_annotation,file_path_refined_state_annotation,file_path_review_intervals,sampling_frequency_in_hz,ECG,BP,EEG,EOG,EMG,EMGamp,Resp,Tabd
WorkPath\Bears11_Bear2\Bear20120112a-Bear2-HH.edf,WorkPath\Processed\Bear20120112a-Bear2-HH.npy,WorkPath\Scores\Bear20120112a-Bear2-HH_OT.edf,WorkPath\Scores\Bear20120112a-Bear2-HH_OT_adj.edf,WorkPath\Processed\Bear20120112a-Bear2-HH_annotation_predicted.edf,WorkPath\Processed\Bear20120112a-Bear2-HH_refined_annotation.csv,WorkPath\Processed\Bear20120112a-Bear2-HH_review_intervals.csv,500,ECG,BP,EEG,EOG,EMG,EMGamp,Resp,Tabd
WorkPath\Bears11_Bear2\Bear20120203a-Bear2-HL.edf,WorkPath\Processed\Bear20120203a-Bear2-HL.npy,WorkPath\Scores\Bear20120203a-Bear2-HL_OT.edf,WorkPath\Scores\Bear20120203a-Bear2-HL_OT_adj.edf,WorkPath\Processed\Bear20120203a-Bear2-HL_annotation_predicted.edf,WorkPath\Processed\Bear20120203a-Bear2-HL_refined_annotation.csv,WorkPath\Processed\Bear20120203a-Bear2-HL_review_intervals.csv,500,ECG,BP,EEG,EOG,EMG,EMGamp,Resp,Tabd
25 changes: 20 additions & 5 deletions example_pipeline/00_truncate_edf_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@

if __name__ == '__main__':

from configuration import (
time_resolution,
)

# --------------------------------------------------------------------------------
# parse and check inputs

Expand All @@ -34,10 +38,12 @@
check_dataframe(datasets,
columns = [
'file_path_raw_signals',
'file_path_manual_state_annot_orig',
'file_path_manual_state_annotation',
],
column_to_dtype = {
'file_path_raw_signals' : str,
'file_path_manual_state_annot_orig' : str,
'file_path_manual_state_annotation' : str,
}
)
Expand All @@ -46,14 +52,19 @@
datasets = datasets.loc[np.in1d(range(len(datasets)), args.only)]

for ii, dataset in datasets.iterrows():
print("{} ({}/{})".format(dataset['file_path_manual_state_annotation'], ii+1, len(datasets)))
print("{} ({}/{})".format(dataset['file_path_manual_state_annot_orig'], ii+1, len(datasets)))

file_path_raw_signals = dataset['file_path_raw_signals']
with EdfReader(file_path_raw_signals) as f:
total_time_in_seconds = f.file_duration
#truncate to whole number of epochs
total_time_in_seconds = time_resolution*(total_time_in_seconds // time_resolution)

file_path_manual_state_annotation = dataset['file_path_manual_state_annotation']
states, intervals = _load_edf_hypnogram(file_path_manual_state_annotation)

file_path_manual_state_annot_orig = dataset['file_path_manual_state_annot_orig']


states, intervals = _load_edf_hypnogram(file_path_manual_state_annot_orig)

start, stop = intervals[-1]
while stop > total_time_in_seconds:
Expand All @@ -63,5 +74,9 @@
elif (start < total_time_in_seconds) and (stop > total_time_in_seconds):
intervals[-1] = (start, total_time_in_seconds)
start, stop = intervals[-1]

_export_edf_hypnogram(file_path_manual_state_annotation, states, intervals)

with EdfReader(file_path_manual_state_annot_orig) as f:
edf_header = f.getHeader()

file_path_manual_state_annotation = dataset['file_path_manual_state_annotation']
_export_edf_hypnogram(file_path_manual_state_annotation, states, intervals, edf_header)
8 changes: 5 additions & 3 deletions example_pipeline/02_test_state_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,17 @@

# LDA tranformed signals
transformed_signals = annotator.transform(signal_arrays[ii])
plot_signals(transformed_signals, ax=axes[1])
plot_signals(transformed_signals, ax=axes[1], sampling_frequency=1/time_resolution)
axes[1].set_ylabel("Transformed signals")

predicted_state_vector = annotator.predict(signal_arrays[ii])
predicted_states, predicted_intervals = convert_state_vector_to_state_intervals(predicted_state_vector, mapping=int_to_state)
predicted_states, predicted_intervals = convert_state_vector_to_state_intervals(
predicted_state_vector, mapping=int_to_state, time_resolution=time_resolution)
plot_states(predicted_states, predicted_intervals, ax=axes[2])
axes[2].set_ylabel("Automated\nannotation")

states, intervals = convert_state_vector_to_state_intervals(state_vectors[ii], mapping=int_to_state)
states, intervals = convert_state_vector_to_state_intervals(
state_vectors[ii], mapping=int_to_state, time_resolution=time_resolution)
plot_states(states, intervals, ax=axes[3])
axes[3].set_ylabel("Manual\nannotation")

Expand Down
9 changes: 8 additions & 1 deletion example_pipeline/04_run_state_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import numpy as np
import matplotlib.pyplot as plt

from pyedflib import EdfReader, EdfWriter
from somnotate._automated_state_annotation import StateAnnotator
from somnotate._utils import (
convert_state_vector_to_state_intervals,
Expand Down Expand Up @@ -89,11 +90,13 @@ def _get_score(vec):
# check contents of spreadsheet
check_dataframe(datasets,
columns = [
'file_path_raw_signals',
'file_path_preprocessed_signals',
'file_path_automated_state_annotation',
'file_path_review_intervals',
],
column_to_dtype = {
'file_path_raw_signals' : str,
'file_path_preprocessed_signals' : str,
'file_path_automated_state_annotation' : str,
'file_path_review_intervals' : str,
Expand All @@ -114,7 +117,11 @@ def _get_score(vec):
predicted_state_vector = annotator.predict(signal_array)
predicted_states, predicted_intervals = convert_state_vector_to_state_intervals(
predicted_state_vector, mapping=int_to_state, time_resolution=time_resolution)
export_hypnogram(dataset['file_path_automated_state_annotation'], predicted_states, predicted_intervals)

with EdfReader(dataset['file_path_raw_signals']) as f:
edf_header = f.getHeader()

export_hypnogram(dataset['file_path_automated_state_annotation'], predicted_states, predicted_intervals, edf_header)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behaviour is implemented in c020140. However, the headers are only copied, if the target file for the annotations is indeed in an EDF format.


# compute intervals for manual review
state_probability = annotator.predict_proba(signal_array)
Expand Down
11 changes: 6 additions & 5 deletions example_pipeline/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ def _chebychev_bandpass(lowcut, highcut, fs, rs, order=5):
('Sleep stage 1 (artefact)', -2),
('Sleep stage R' , 3),
('Sleep stage R (artefact)', -3),
('undefined' , 0),
('Sleep stage ?' , 0), # ? = Undefined/unscorable code in Polyman
('Movement Time' , 0), # Just included in case M code is accidentally added to avoid exception, cannot be deleted in Polyman
])

# Construct the inverse mapping to convert back from state predictions to human readabe labels.
Expand All @@ -167,7 +168,7 @@ def _chebychev_bandpass(lowcut, highcut, fs, rs, order=5):
'N' : 'Sleep stage 1 (artefact)',
'r' : 'Sleep stage R' ,
'R' : 'Sleep stage R (artefact)',
'x' : 'undefined' ,
'x' : 'Sleep stage ?' , # ? = Undefined/unscorable code in Polyman
'X' : 'undefined (artefact)' ,
}

Expand All @@ -179,7 +180,7 @@ def _chebychev_bandpass(lowcut, highcut, fs, rs, order=5):
'Sleep stage 1 (artefact)' : 'cornflowerblue',
'Sleep stage R' : 'gold',
'Sleep stage R (artefact)' : 'yellow',
'undefined' : 'gray',
'Sleep stage ?' : 'gray', # ? = Undefined/unscorable code in Polyman
'undefined (artefact)' : 'lightgray',
}

Expand All @@ -190,7 +191,7 @@ def _chebychev_bandpass(lowcut, highcut, fs, rs, order=5):
'Sleep stage 1 (artefact)' ,
'Sleep stage R' ,
'Sleep stage R (artefact)' ,
'undefined' ,
'Sleep stage ?' , # ? = Undefined/unscorable code in Polyman
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Polyman terminology for undefined states adopted in 7059e9f.

'undefined (artefact)' ,
]

Expand All @@ -213,7 +214,7 @@ def _chebychev_bandpass(lowcut, highcut, fs, rs, order=5):
# --------------------------------------------------------------------------------

# desired time resolution of the automated annotation (in seconds)
time_resolution = 1
time_resolution = 10

# default view length when manually annotating states
default_view_length = 60.
Expand Down
13 changes: 10 additions & 3 deletions example_pipeline/data_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
import pandas

from argparse import ArgumentParser
from collections import Iterable
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python v3.3 changes to collections addressed in 58924e0

try:
# Python <= 3.9
from collections import Iterable
except ImportError:
# Python > 3.9
from collections.abc import Iterable
from pyedflib import EdfReader, EdfWriter
from six import ensure_str

Expand Down Expand Up @@ -338,7 +343,7 @@ def _load_edf_hypnogram(file_path):
return states, intervals


def _export_edf_hypnogram(file_path, states, intervals):
def _export_edf_hypnogram(file_path, states, intervals, header=None):
"""Export hypnogram as "Time-stamped Annotations Lists (TALs)" in
EDF annotations.

Expand All @@ -361,8 +366,10 @@ def _export_edf_hypnogram(file_path, states, intervals):
with EdfWriter(file_path, 0) as writer:
for (start, stop), state in zip(intervals, states):
writer.writeAnnotation(start, stop-start, state)
if header:
writer.setHeader(header)
writer.close()


# --------------------------------------------------------------------------------
# aliases
Expand Down